Continuous delivery

Continuous Integration vs Continuous Delivery vs Continuous Deployment

Wiele razy słyszałem jak ludzie mylą Continuous Delivery z Continuous Deployment dlatego zdecydowałem się tutaj pokazać różnice, zaczynając przy okazji od Continuous Integration. Bez obaw, postaram się unikać słownikowych definicji.
Wyobraźmy sobie, że chcemy ocenić poziom zaawansowania procesu wytwarzania oprogramowania jaki obowiązuje w naszym projekcie. Możemy tu wydzielić następujące etapy:

Level 0: None

Wszystko robione jest ręcznie. Programista bierze skądś kod źródłowy (odpowiedni folder na dysku, może nawet jakieś swoje repo w sieci), kompiluje i tworzy paczkę, którą może zainstalować/uruchomić na docelowym środowisku. Jego własna ekspercka wiedza potrafi ocenić czy paczka którą stworzył jest “dobra” (może nawet bardzo skrupulatnie przetestować to co zrobił). Jeżeli nad kodem pracuje więcej niż jedna osoba, to co jakiś czas synchronizuje swoje zmiany z innymi członkami zespołu, rozwiązuje powstałe konflikty i ustala czy nic się nie zepsuło.
Wydawać by się mogło, że nikt już z tego nie korzysta, ale przecież właśnie tak zaczynał się kiedyś każdy domowy/studencki projekt. Nie można mówić, że to coś złego (pewnie gorsze byłoby tworzenie od początku skomplikowanego procesu aby zrobić prosty programik).

Level 1 (Normal): Continuous Integration

Każda zmiana w kodzie jest integrowana z resztą systemu w sposób ciągły. Ciągły, czyli jak najczęściej to możliwe – czasem na poziomie poszczególnych commitów, czasem pull-requestów. W jaki sposób można ocenić, że integracja się powiodła? Po pierwsze dołączenie zmiany do reszty systemu jest możliwe (nie ma konfliktów w systemie kontroli wersji kodu). Po drugie zmiana nie psuje działania reszty systemu (tutaj potrzebne jest uruchomienie testów automatycznych, przynajmniej testów jednostkowych). Integracja odbywa się w sposób ciągły, za każdym razem kiedy zmiana trafia do publicznego repozytorium. Czy jak ktoś wysyła zmiany na serwer raz w tygodniu to dalej jest continuous integration? Tak, ale to ewidentne proszenie się o konflikty. Od tego jest Continuous Integration (CI) aby integrować się jak najczęściej i rozwiązywać malutkie konflikty a nie raz w tygodniu przepisywać koncepcję na od nowa.

Level 2 (Hard): Continuous Delivery

Celem jest ciągłe posiadanie stabilnej paczki którą możemy w prosty sposób wrzucić na produkcję (środowisko docelowe). Oprócz wszystkiego co daje nam CI musimy też mieć możliwość łatwego przeprowadzenia deploymentu (najczęściej one-click deployment). Duży nacisk kładzie się właśnie na automatyzacje wszelkich zmian w konfiguracji aplikacji, migrowanie baz danych, tworzenie/podłączanie nowych zasobów (serwisów, baz, kolejek itp). W związku z częstszym wdrażaniem paczek na produkcję należy ulepszyć proces przywracania poprzedniej wersji aplikacji (czasem błędy wychodzą dopiero na produkcji). W związku z tym często rozszerza się sposób kontroli wersji wprowadzając GitFlow lub jakąś własną implementację tego procesu. Aby ograniczyć ilość pracy związanej z ciągłym testowaniem paczki przed wdrożenie rozbudowuje się testy automatyczne. W przypadku aplikacji webowych wprowadza się testy API/UI aby uniknąć regresji i testować nowe funkcjonalności z punktu widzenia użytkownika (również w połączeniu z innymi zależnościami takimi jak połączenie z bazą danych i innymi serwisami).
Koszt jest dużo wyższy niż w przypadki CI, ale zaletą jest możliwość dostarczania nowej wersji aplikacji w krótkich odstępach czasu (w przypadku SCRUMa co sprint lub nawet częściej).

Level 3 (Expert): Continuous Deployment

To już jest jazda bez trzymanki połączona z grą na akordeonie. Celem jest aby każda zmiana od razu trafiała na środowisko produkcyjne. Nie ma tu miejsca na każdorazową decyzje człowieka jeśli chodzi o wdrażanie nowej paczki. W związku z tym konieczne jest rozbudowanie systemu monitorowania aplikacji i wczesne zgłaszanie błędów. Lepiej żeby proces sprawdzania paczki trwał dłużej i zatrzymał się przy końcu niż przepuścił wersję która zawiera błąd i psuje dane użytkownika. Dlatego w tym przypadku oprócz dopieszczenia testów automatycznych wprowadza się sprawdzanie code coverage bliskiego 100% (nie można pozwolić, aby na produkcję trafił niepewny kawałek kodu). Oczywiście nie da się zaimplementować gotowej funkcjonalności przy pomocy kilku commitów, dlatego bardzo często stosuje się tzw. “feature switches” które pozwalają włączyć funkcjonalność w sposób kontrolowany tylko określonym użytkownikom lub stopniowo (tzw “canary release”). Deployment też należy udoskonalić w taki sposób, aby nie wpływał na sposób działania aplikacji (system musi działać bez widocznych przerw).
Continuous Deployment pozwala na zbudowanie najlepszej relacji między twórcą aplikacji a jej użytkownikami, jednak koszt wprowadzenia takiego rozwiązania jest ogromny. Często mogą sobie na to pozwolić tylko największe serwisy. W tym samym miejscu jest to programistyczne El Dorado dla osób zajmujących się procesem wytwarzania oprogramowania.

Myślę, że coś takiego jak Level 4 już nie istnieje, ale gdyby przyszło wam coś do głowy, to czekam na komentarze.

Advertisements
Programming (back-end), Programming (front-end)

Czy można ignorować testy?

Wiele razy słyszałem, że jak ma się ignorować testy to już lepiej je usunąć. Dlaczego? Bo bardzo łatwo podjąć decyzję o zignorowaniu testu (dodaniu jednego atrybutu czy tam literki “x” w przypadku JavaScript) a trudniej podjąć decyzję o od-ignorowanie testu. Tutaj znajdzie się już milion powodów, żeby tego nie robić: a bo nie ma czasu, a bo to trzeba zmienić coś innego co już działa, a bo ten test to od początku pisała jakaś ciamajda i trzeba go przepisać i to potrwa.
A więc czemu usunąć? Bo i tak nie są uruchamiane i starzeją się z każdą chwilą. To jest “Dead code” który przy okazji maskuje inny “dead code” (normalnie na klasie dostalibyśmy “0 references”, ale przecież jest używana w tym martwym teście).

Co w takim razie robić? Mi do głowy przychodzą następujące pomysły:

  • Naprawić test
  • Napisać test od nowa
  • Usunąć test i przy testowanej metodzie dodać “TODO” (może ktoś te TODO przegląda)
  • Dodać do listy długu technicznego naprawę tego testu (my używamy specjalnych tasków w backlogu)

A co jak już musisz ignorować, bo chcesz naprawić ten nieszczęsny test ale czujesz na plecach oddech Product Ownera? Wtedy można ignorować test, ale mądrzej:

  • Niektóre frameworki pozwalają dodać datę do kiedy test będzie nieaktywny, np..
    [Ignore("XYZ external service down", Until = "2017-12-14 12:00:00Z"]
  • Jeśli aktualnie nie możesz skorzystać z takiego atrybutu, zawsze możesz zastosować prosty trik, dodając na początku testu:
    If (DateTime.Now > new DateTime(2017, 12, 14)) { return; }
    Ważne jest to, aby Ci system sam przypomniał, że masz coś do zrobienia.

Jeśli znajdziesz w swoim frameworku fajny sposób na czasowe wyłączenie testów to podziel się w komentarzu. Jeżeli jesteś innego zdania – tym bardziej.

Articles

[PL] Przechodzę na polski / Switching blog language to Polish

For english-speaking readers – I decided to switch blog language to Polish as an experiment. I want to check if that will increase freaquency of publishing new posts or increase number of visitors. At the same time some universal posts may still be in English so don’t disconnect.

Wierzę, że “Polcy i Polaki” zrozumieli. Raz kozie śmierć! Chciałbym sprawdzić, czy po polsku uda się pisać szybciej a w rezultacie częściej i więcej. Mimo wszystko mój styl bycia, żartowania i pisania jest właśnie polski. Poza tym część rzeczy o których chcę pisać odnosi się konkretnie do polskiego środowiska, pracy, prawa i trudno byłoby to wszystko pisać/czytać w języki angielskim.
Na początku zakładałem, że pisząc po angielsku piszę do wszystkich. Ale jak coś jest do wszystkiego, to jak wiemy jest do niczego. Odnosząc to do bloga, czasem mam wrażenie, że jest do nikogo, nie ma życia. A chciałbym właśnie pisać do konkretnych grup ludzi. A może oni też woleliby komentować pod artykułem po polsku? Zobaczymy.
Z drugiej strony, jeżeli będę chciał pisać o rzeczach wyjątkowo technicznych, to pewnie zrobię to po angielsku – nie ma co pisać po polsku i co chwilę wciskać jakiś makaronizm. Poza tym jeśli chciałbym odesłać kogoś to takich uniwersalnych treści, to jednak będzie łatwiej.

Zobaczymy co z tego wyjdzie. Nie wykluczam, że taka wersja bloga zacznie mi przeszkadzać, że to wcale nie chodziło o język i wrócimy do angielskiego. A co mi tam, mój blog, nic nie tracę przecież. Kto się zgadza ręka w góre, plusik w komentarzu 🙂

Continuous delivery

Code inspection on build server using ReSharper Command Line Tools

Did you see source code with VS scrollbar full of different colors and warning sign in every file? For a long time that was my case. It did not help that we agreed on some coding guidelines within our team. If some rules are not enforced, then code issues will get into your source code – doesn’t matter if it’s rush, manual refactoring, merging conflicts or less careful teammates. To avoid that the best way is to make code inspection part of your bulid pipeline.

resharperClt6

Option 1: Using VSTS

  1. Add “Resharper Code Quality Analysis” task to your build definitionresharperClt1
  2. Specify solution file and inspection severity:resharperClt2

Done! Next time you run your bulid it will fail if there are any code issues with given severity (or higher).

Option 2: Using Powershell

Use the following powershell script in order to check code issues using Resharper CLT:

$slnFile = ".\YourSolutionFile.sln"
$settingsFile = ".\YourSolutionFile.sln.DotSettings"
$severity = "WARNING"
$outputFile = ".\inspect-code-log.xml"
#just a container for Resharper CLT Nuget
$projectForResharperClt = ".\test\YourSolutionFile.SampleTestProject\YourSolutionFile.SampleTestProject.csproj"
$packageDirectory = ".\packages"

#Preparing inspectCode tool
& dotnet add $projectForResharperClt package JetBrains.ReSharper.CommandLineTools --version 2017.2.0 --package-directory $packageDirectory

#running code analysis
& $packageDirectory\jetbrains.resharper.commandlinetools\2017.2.0\tools\inspectcode.exe --profile=$settingsFile $slnFile -o="$outputFile" -s="$severity"

#processing result file
[xml]$xml = gc $outputFile
if ($xml.Report.Issues.ChildNodes.Count -gt 0)
{
   write-error ("`nCode analysis failed: `n" + ((gc $outputFile) -join "`n"))
}
else
{
   echo "No issues found"
}

Build result

If the build fails on code quality check you should see the following result (sample screenshot from my VSTS build):

resharperClt3

Why ReSharper Command Line Tools?

ReSharper in general is an obvious choice for everyone using Visual Studio. It means that in most of development teams it is already installed on each and every machine. In terms of code inspection it’s a huge benefit because people do not have to install any tool to visualize code issues. It’s already done in a way they know it. Fixing issues is also familiar which makes fighting them very intuitive and comfortable.

BTW: the newest Rider IDE uses the same *.DotSettings file so everything can be applied there as well.

How to start?

Initially you should start with setting inspection severity to “WARNING”. Even with such level most of projects will get dozens of errors so it’s good to have an initial session with Resharper settings and mark what is really a warning. If you already edited inspection severity for some issues remember to save them to team-shared settings. You can do that by going to Resharper options, searching for overriden settings (indicated with “arrow” icon), then clicking “checkbox” and pressing “Save to -> … team shared”

resharperClt4

You can check stats of your code issues by going to: Resharper -> Inspect -> Code issues in Solution (remember to group by issue severity):

resharperClt5

What next?

If you are fine with your current “Warnings” you may add more rules to check. You can also start eliminating “suggestions” by selecting some of them and changing their severity to “WARNING”. I would avoing changing inspection severity to “SUGGESTION” on build server – in most cases this will complain in too many places and you will not be able to fix them all at once.

Eat the elephant one bite at a time

Personally I do have a reminder in my calendar to do a small-step-improvement and mark one suggestion as “WARNING” in the beginning of the sprint. This way I know that finally I will get rid of all the “SUGGESTIONS” or even “HINTS”. At the same time I will not require any dedicated user-story or “cleanup sprint”.

Command line: VIM

VIM: Better grepping (in Windows)

Several times I noticed that grepping in VIM (finding in files) does not work very well comparing to Visual Studio or VSCode at least. I felt it pretty hard last time when I tried to search in a medium-size web project. It took over 23 seconds to search for files and another 55 seconds to render the list of results. I’ve never experienced something similar in any other editor.  Another think that bothered me was the quality of results which frequently showed binary files, multi-line records or everything else what I could call “rubbish” in the context of file search option:

2017-04-08_22-41-56

The first guess is that alternative editors just skip files that are not source code. And that’s the thing (at least looking at Visual Studio Code file search):

2017-04-08_22-54-55

Unfortunately there is no easy way to tell VIM grepper to exclude locations from search (at least in Windows). The default findstr (you can find more about it in my previous post) does not have such option. Nevertheless there is a way to achieve such behaviour. Just add this snippet to your $VIMRC file:

function! FastGrep(what, ...) abort
 let where = a:0
 if where == ""
    let where = "*.*"
 endif
 let command = "silent ! dir /s/b/a:-d"
 let command = command . " | findstr /v \\node_modules\\"
 let command = command . " | findstr /v \\dist\\"
 let command = command . " | findstr /v \\\.git\\"
 let command = command . " | findstr /v \.map$"
 let command = command . " | findstr /v \.swp$"
 let command = command . " | findstr /v \.ts\.html$"
 let command = command . " | findstr /v ~$"
 " add excludes here ------^
 let command = command . " > \\%home\\%\\solution_files.tmp"
 execute comma              nd
 execute "grep /f:\\%home\\%\\solution_files.tmp ".a:what." ".where
endfunction

:command! -nargs=+ -complete=command Grep2 call FastGrep()

The idea behind the snippet is the following: get list of all the files in the working folder, remove the locations you want to exclude, and then pass the list of files to “findstr” command (/f switch allows to pass the list of input files).

Here’s a quick list of tricks used in the snippet:

execute              - run vim command
!                    - execute shell command
silent               - skip "Press ENTER to continue" after running shell
dir /s/b/a:-d        - get a raw list (/b) of files only (/a:-d) 
                       in all subdirectories (/s) 
findstr /v \.git\    - find lines that does not containing "\.git\"
%home%               - user home directory (i.e. c:\users\user_1)
FastGrep(what, ...)  - function taking one named argument and array of 
                       unnamed arguments
a:0                  - first unnamed argument
:command!            - define VIM command alias
-nargs=+             - alias must have arguments (any number)
             - list of arguments passed to the alias

Now in order to execute such command you just need to run:

:Grep2 myFunction1
:Grep2 myFunction2 *.js

The same operation that took initially 23 seconds was executed in less than 1 second (with rendering results). Of course it showed less records, but only the meaningful ones (a side note: my medium-size web application contained 996 folders inside /node_modules !!).

Remember that you can extend it by your own excludes.

Command line: VIM

VIM: Find in files

It’s not a surprise that there is a built-in way to find in files in VIM. You can use four commands: :grep, :lgrep, :vimgrep, :lvimgrep.

2017-04-02_09-19-29

:grep will use the default “find” tool for your operating system (“grep” for linux family, “findstr” for Windows). :vimgrep will use built-in vim search (same as for “/” searching) which is slower than system search (but works if you have problems with grep/findstr/other). On the other hand vimgrep has the same syntax everywhere (:grep depends on external tool used). Search result is presented using Quickfix window (opened with :copen). :lgrep/:lvimgrep will do the same, but using location list (opened with :lopen). In practise the difference is that if you use Quickfix window (:copen) it will stick to the current editor. Location window will show in the same way, but if you select anything then the window will disappear and you will have to run :lopen again.

I guess you have to choose yourself which command you like most.  The syntax is as follows:

:grep searchpattern locationpattern

i.e.:

:grep MyMethod *.*

Will look for phrase “MyMethod” in all files in current workind directory (you can check currect directory using :pwd). By default it does not perform recursive search (it will not search in subdirectories). And here the fun part starts.

In order to search in subdirectories you would have to use **/*.* pattern. However this works for :vimgrep and :grep in linux, but does not work for Windows. In order to make it work on Windows you would need to use /S parameter (linux grep also accepts -R parameter which does the same thing). You can pass it like that:

:grep /S MyMethod *.*

This will work on Windows and will find all “MyMethod” phrases in all files in all subdirectories of current working directory. However you can still make it the default behaviour in VIM. But first you neeed to know one thing:

:set grepprg

This is a variable that controls the usage of external grep tool in VIM. Depending on your operating system you will have different results:

grepprg=findstr /n    # <--- on Windows
grepprg=grep -n $* /dev/null # <---- on Linux

So in order to use recursive search you have to modify grepprg and add a proper parameter:

grepprg=findstr /S /n                  # <--- on Windows
grepprg=grep -nR $* /dev/null          # <---- on Linux

The same way you can add more parameters or make more complex commands.

Command line

CMDer/ConEmu – starting new tab in current folder

Recently I posted about a trick allowing the user to open new CMDer/ConEmu tab in current folder from command line. In response ConEmu team twitted a better solution that I would like to share:

2017-03-30_22-00-53

I don’t want to have specific shortcut for each task so I just edited default {cmd} task this way:

2017-03-30_21-51-35

The key here is to add “/dir “%CD%” as task parameter and remove “:d:”%USERPROFILE%” parameter from the textbox on the bottom. Just after that when you add new tab (in my case Ctrl + Shift + T) you can specify “Startup directory for new process” or leave it empty. In the latter case (and only then!) ConEmu will open new task in current directory.

2017-03-30_22-04-17

Why is this better?

The previous solution required active console in order to execute some command. Now everything is done by ConEmu, so you can have some process running (web server/test watch/vim etc) and at the same time start new tab in the same folder.

Thanks a lot ConEmu Team!