Do Agile retros work at all?

Ever since I started working in software, all teams except for one had a weekly or biweekly ritual of an agile retrospective session (which I will be calling “retro” below).

In principle, retros are supposed to let teams improve how they work by reflecting on the last sprint.

In practice, I don’t think I ever experienced any noticeable improvement that would stick for a long time.  It makes me question whether retros are worth the time in most teams.

I think there are 2 main reasons for that:

  1. Many teams, especially bigger ones, struggle to find solutions that everybody agrees on.
  2. For many problems, the causes can not be realistically changed, because they are outside of the team’s authority.

The first problem – lack of agreement – is a hard social problem, but at least is amenable to better ways of having meetings.  I’ve seen some initiatives of teams trying to improve the way they hold meetings and reach agreements, but usually, not everyone agreed to conclusions, and these things tend to not work unless they get buy-in from everyone. Someone some changes are implemented, but after a few weeks, things revert to the old ways.

The second problem – that the causes of team problems are effects of company-wide mandates or team lead’s opinion.  This is an even harder problem because I don’t think there is a way out of it from the team’s perspective.

I think that the worst part of that is that team is supposed to try to improve with retros against these odds.

How are your retros?

A simple test can be done by answering 2 questions:

  1. Can you track any changes to a retro session?
  2. When you look at actual changes of how work in the team gets done, did they happen by retro (and retro alone)?

In my experience, when any meaningful changes in how team works happen, they come from the team lead or upper management.  If you have counter-examples, please do write to me and tell me what was the change and how did it happen.

Vim autocommands

I prepared these notes as part of the meetup event (Vimprovement 5), but they might be useful even if you haven’t joined.

Autocommands are the way to execute any vim actions (like setting an option or mapping a key) in response to various events. Events recognised by vim include writing or reading a file, detecting a filetype, and a lot more.

Autocommands can be set by plugins, or by yourself in your vimrc. The most common category of user-defined autocommands is setting options for particular filetype.

You need to wrap your autocommands in the augroup sandwich:

augroup GROUP_NAME
   <here go your autocommands>
 augroup END

This makes sure that your vimrc can be safely sourced multiple times. Without augroup and autocmd! part, your autocommands would not be cleared, and effectively duplicated, which can cause problems.

Here’s how to set different options for different filetypes:

augroup GROUP_NAME
   autocmd FileType python set local shiftwidth=2
   autocmd FileType c set local shiftwidth=8
 augroup END

The general syntax is:
autocmd <event> <pattern> <command>

To see all the events recognized by vim, run :h autocommand-events. There’s over one hundred of them!

<pattern> may have different meaning for different events. For reading- and writing-related events, it’s usually a filename. Another example is FileType event; with it, <pattern> is a filetype, like javascript. If an event does not give any meaning to <pattern>, you still need to put something between <event> and <command> – use * in that case.

Here are some more exotic examples of autocommands, with commands explaining what they are doing:

  " Reload my vimrc everytime I save it.
  autocmd BufWritePost ~/.vimrc source % | redraw | echo "Reloaded vimrc!"
  " Open temporary, readonly buffer with text extracted from a pdf file.
  autocmd BufReadPost *.pdf
        \ edit <afile>:r |
        \ set buftype=nofile |
        \ set bufhidden=delete |
        \ set readonly |
        \ %!pdftotext <afile> -
  " Open temporary, readonly buffer with un-minified JavaScript.
  autocmd BufReadPost *.min.js
        \ edit <afile>:r:r |
        \ set buftype=nofile |
        \ set bufhidden=delete |
        \ set readonly |
        \ %!unminify <afile> -
  " Project-specific settings - run prettier on JavaScript files, but only in that directory (recursively)
  autocmd BufWritePost /Users/mdr/proj/vimprovement/5_2020_12_14/project/*.js silent !npx prettier --write 
  autocmd BufWritePost /Users/mdr/proj/vimprovement/5_2020_12_14/project/*.js redraw!
  " Save buffer when idle (only when there are changes)
  autocmd CursorHold * update
augroup END

The PDF trick requires pdfutils command to be available (brew install poppler to get it, if you’re on MacOS with Homebrew).
The JavaScript trick requires the unminify command to be available (npm i -g unminify).

If you want to check out all my autocommands, see this gist.

Other things, not mentioned yet (you can learn about them in :h autocommands):

  • <buffer>
  • Using multiple events and multiple patterns in a single autocmd
  • Built-in gzip functionality as an example of sophisticated use of autocommands

Examples shared by participants

Here are autocommand examples shared in the chat during the event. Thank you!

From Nikhil – setting mappings for Python files:

autocmd FileType python nnoremap <silent> <leader>r :w !python3.8<CR>
autocmd FileType python vnoremap <silent> <leader>r :w !python3.8<CR>
autocmd FileType python nnoremap <silent> <leader>i :!bpython -q -i %<CR>

From Lucas – example of alternative to autocmd for filetype-specific configuration:

$ cat ftplugin/python.vim 
setlocal formatprg=black\ --quiet\ -
nnoremap <buffer> <F5> :up\|!python "%"<CR>
nnoremap <buffer> <F7> mfgggqG`fzz

From Andres – toggle relativenumber option when entering and leaving insert mode:

au InsertEnter * :set norelativenumber
au InsertLeave * :set relativenumber

From Andres – session setup when entering and leaving vim:

 function! MakeSession()
     let b:sessiondir = $HOME . "/.vim/sessions" . getcwd()
     if (filewritable(b:sessiondir) != 2)
         exe 'silent !mkdir -p ' b:sessiondir
     let b:filename = b:sessiondir . '/session.vim'
     exe "mksession! " . b:filename

 function! LoadSession()
     let b:sessiondir = $HOME . "/.vim/sessions" . getcwd()
     let b:sessionfile = b:sessiondir . "/session.vim"
     if (filereadable(b:sessionfile))
         exe 'source ' b:sessionfile
         echo "No session loaded."

au VimEnter * nested if argc() == 0 | :call LoadSession() | endif
au VimLeave * if argc() == 0 | :call MakeSession() | endif

From Nick – running media files with default programs:

(OpenSystemFile would be a operating-system specific function that runs a default program associated with a filetype)

    autocmd BufEnter *.jpg call OpenSystemFile()
    autocmd BufEnter *.png call OpenSystemFile()
    autocmd BufEnter *.gif call OpenSystemFile()
    autocmd BufEnter *.pdf call OpenSystemFile()
augroup END

From Andres – highlight yank:

au TextYankPost * silent! lua vim.highlight.on_yank {higroup="IncSearch", timeout=300}

From Nazar – automatically rebalance windows on vim resize:

autocmd VimResized * :wincmd =

From Martin – project-specific options:

au BufNewFile,BufRead /private/var/*/gopass* setlocal noswapfile nobackup noundofile

Vim insert mode tips & tricks

One of the first things you learn when starting using vim is that you spend a lot of time in the normal mode, and that’s where all the interesting actions happen. Insert mode is there just for inserting literal text.

Well, that’s only part of the story.  There are actually some quite interesting things you can do while in insert mode.

This post is about those things.

There is one feature that most users already know: autocompletion.  I will leave it as a separate topic, and also, I think it’s pretty well covered in other places already.

  • Iabbrev
  • imap
  • <C-o>
  • <C-R>
  • Surprising ways to use iabbrev and imap
  • Picking ergonomic mappings
  • More examples


Iabbrev is a feature that expands abbreviation.

For example, you might want to have this In your vimdc:

iabrev mmm

And now, whenever you type “mmm” and then any character other than a letter or number (like a space, or a dot), “mmm” will be replaced with “”.

If there are any characters right before the abbreviation, it will prevent the replacement from happening.
So, if you type “ommm”, it will not be replaced.

Iabbrev can be used not only for convenient phrases. It can be used for fixing typos you are prone to make. For example, I tend to misspell “daemon” as “deamon”. So I can put this in my .vimrc:

Iabbrev deamon daemon

Now, if I misspell it, vim will correct it for me.

I have one problem with this approach, though: I might not even notice that I made a typo, and vim can spoil me to never have to learn to type the word correctly.  However, I can make vim force me to correct the misspelling myself, so that I learn to spell the word correctly:

iabbrev deamon dAEmon TYPO!!!

Now if I misspell it, it will be replaced with “dAEmon TYPO!!!”, so I can see it and correct it manually, thus breaking my bad habit.

If you use iabbrev this way , you might want to check out vim-abolish.  It provides a way to conveniently create abbreviations for multiple variants of words.


imap establishes mappings for insert mode.

You might have used its more popular cousin, nmap.

imap, just as iabbrev, is a way to make vim do something when you type something in the insert mode – but it is more general.

Let’s look at some examples.

Note about the examples: I’m using the “nore” variant of imap, i.e., inoremap, to avoid recursive mappings. It’s almost always what you want.

You can have a mapping that will simulate pressing the Escape key, in order to get back to the normal mode:

inoremap ji <esc>

Now you can exit the insert mode without reaching for the Escape key!

You can have a mapping to exit the insert mode, save the buffer, and return to the insert mode:

inoremap ,.s <esc>:w<cr>a

The main difference from iabbrev is that imap doesn’t care about the word boundaries. iabbrev foo bar will ignore “xfoo” or “foox”; but imap foo bar will replace “xfoo” with “xbar”. Also, imap does not wait until you enter any other keys after the key sequence you mapped.

It gives you more freedom, but you need to be more careful with avoiding key sequences that you might want to insert.

For example, if you would do this mapping:

inoremap ing ANYTHING

…you just locked yourself out from inserting any word that includes “ing”!

If you end up in a situation where you need to bypass a mapping, you can do one of 2 things:

  1. Run “:iunmap <key sequence>” (in our example, it would be “:iunmap ing”).  It will remove the mapping until you start vim again.
  2. Press Ctrl-V before the sequence.  It forces the next character to be inserted literally, bypassing any mappings (it doesn’t have anything to do with Windows shortcut for pasting).

Finding good candidates for imap and iabbrev

List of 3-character sequences that don’t occur in the English dictionary (at least the one I had installed on my linux box).

It’s over 11 000 potential mappings!  You won’t run out of good mappings.

You might want to take any other languages you use into account.  If you want to compose your own list, you can use my hacky script.

I recommend picking sequences which don’t require the same finger to be twice in a row; e.g., “jjj” and “jmu” are bad; “dhf” and “dfs” are good (assuming QWERTY layout).


When in insert mode, Ctrl-o will allow you to perform a single normal mode operation.

Note that a single operation does not always mean a single key. “j”, “dd”, and “:w!” are some examples where each counts as a single operation, and can be done from Ctrl-o.

You can read more in vim’s help system: :help i_CTRL-O.


When in insert mode, pressing Ctrl-r and a register name will put the content of that register at the cursor.

You can read more in vim’s help system: :help i_CTRL-R.

More examples

Special characters

My native language has letters that are not ASCII, which sometimes is a problem if the computer I happen to be using doesn’t have input methods set for me.

I can use imap to have a convienient shortcut for inserting the problematic characters:

inoremap <C-y>o ó

(You can use it also with digraphs, but that’s another topic)

Filetype-specific mappings and abbreviations

You can combine imap and iabbrev with autocommands to have filetype-specific mappings and abbreviations.

Here, I’m setting <C-L> to be translated to ” => ” in several programming languages where it’s used, and setting an abbreviation of “ret” to “return”:

  autocmd FileType javascript,php,ruby inoremap <buffer> <C-L> <space>=><space>
  autocmd FileType javascript,python iabbrev <buffer> ret return
augroup END

<buffer> makes the mapping or abbreviation local to the current buffer.

Insert current date

I use this one a lot. “dst” will be replaced today’s date:

iabbrev <silent> dst <C-R>=strftime("%Y-%m-%d")<cr>

It takes advantage of <C-R>= to run a vim function, strftime.

Current file name

Insert current file’s name, without extension.  I find it useful in codebases when the convention of naming classes has to be consistent with filenames (e.g., “” contains class “SomeThing”).

iabbrev <silent> crr <C-R>=expand('%:t:r')<cr>

Insert result of external command

This allows to insert output of any UNIX command, without ever exiting insert mode.

function! ExternalCommandResult()                                                                                                                         
  return system(input('Command: '))[:-2]                                                                                                                  
inoremap <C-R>! <C-R>=ExternalCommandResult()<cr>                                                                                                                    

Now try <C-R>!, then  type “date”, and press Enter.

Vim: straightforward LSP setup with autocompletion.

This is a quick guide to setting up Language Server Protocol ( for vim.

You will need Vim 8 or NeoVim.

LSP setup consists of a client and one or more servers.

The client is installed as a vim plugin.  Servers are easily installed with another vim plugin.

Install the plugins

Install the following plugins with your favorite plugin management solution, like, or Vim 8’s native packages (see :help packages for more):

Vim-lsp is the LSP client.

Vim-lsp-settings is a companion plugin that provides a convenient way of installing LSP server configurations.

Asyncomplete is a completion engine for Vim, and asyncomplete-lsp is a companion plugin that configures it to work with vim-lsp.

Configure an LSP server

Open a file of the type you want to have LSP enabled for.  For example, I work with TypeScript a lot, so I open any file with the .ts extension.

You can confirm that vim recognizes the filetype correctly by running :set ft? in Vim and looking at the output.

Then, run :LspInstallServer, a command provided by vim-lsp-settings.

It will prompt you to choose a server and then install it. Note down what’s the name of the server, because you will probably want to find documentation for it at some point.

After that succeeds, restart vim, open the file again, and run :LspStatus.

If it prints running, you’re set!

Note that vim-lsp-settings doesn’t have configuration available for every single LSP server in existence, so even if it tells you that no server can be found, it doesn’t necessarily mean that there is no LSP server for the language of your choice.

If you can find a server implementation on, you can configure it manually by following the documentation of vim-lsp.

Usage and customization

Take a look at the documentation of vim-lsp and the documentation of the LSP server you’re using to learn what operations are available.

To get started, type :Lsp and <C-d> to see all commands provided by vim-lsp, but note that some of them might not work with every LSP server.

You will probably want to set up mappings for the commands you want to use often. Here are mine:

nnoremap <leader>dd :LspDefinition<cr>
nnoremap <leader>dn :LspNextDiagnostic<cr>
nnoremap <leader>dp :LspPreviousDiagnostic<cr>
nnoremap <leader>df :LspReferences<cr>
nnoremap <leader>dr :LspRename<cr>
nnoremap <leader>ds :LspStopServer<cr>
nnoremap <leader>dp :LspPeekDefinition<cr>
nnoremap <leader>da :LspCodeAction<cr>
nnoremap <leader>dh :LspHover<cr>
nnoremap <leader>df :LspDocumentFormat<cr> 
nnoremap <leader>dd :LspDefinition<cr>

One thing that seems missing to me in the set of commands provided by vim-lsp is a way to disable and enable LSP manually. But it turns out you can do it by calling the function lsp#disable():

:call lsp#disable()

You can map it as well:

nnoremap <leader>dq :call lsp#disable()

There is also a symmetrical function lsp#enable().

Notes on Vim compilation options

Vim-lsp docs mention that having a Vim installation with Lua support has performance benefits for some features.

You can check if you’re installation has the Lua support, run :version and see if it is included (+lua) or not (-lua).

To compile vim with your selection of features, follow


Vim-lsp is not the only LSP client available for Vim users.
Some alternatives:


Radical idea: your multi-page app should not be a SPA.

Today’s rant is inspired by yet another website that managed to waste 20 minutes of my time. It was a web-app that lost all of my input after I spent that time filling the forms and tried to submit. The app belongs to a international corporation and probably cost hundreds of thousands of dollars to develop, but it’s not unusual.

So, somehow, the brightest idea of web development in the last couple of years is that everything should be a single page application. Interactive game? Make it a SPA. A single-page website with some content? SPA. Multi-page series of forms? You guessed it, SPA.

I’m here to tell you that it’s not, usually, a very good idea. Why? For starters, you are abandoning basic web browser features, because your app breaks some assumptions browser makes about web content.

Here’s a partial list of things that you lose, and have to re-implement:

  • History operations
  • Reload-friendliness
  • Deep linking
  • Basic accessibility features
  • Working on devices/browsers that can’t even JavaScript
  • Search-engine readability

All those features have been part of standard web browsers for decades, and they worked. Now, every other website is an all-JavaScript SPA shit-show, and almost every single one of them has a buggy re-implementation of basic web browser features. Sure, there are libraries out there, but they still need non-trivial amounts of custom code to use them, and that code is often buggy. “Back” and “forward” buttons don’t work; reloading a page does moves user to the main page; clicking on links does not work. We go out of our way just to add more code that can break. Why are we doing it to ourselves and our users?

I get it – the code surrounding navigation has some tricky bits, and it is boring to test it thoroughly. But guess what? – users don’t care about that.

Maybe you think you are special and you will not make any of those mistakes? I envy your confidence, but I’m sorry for your users. Because companies like Twitter and Facebook – with all their billions of dollars and armies of (supposedly) the smartest programmers – routinely cause navigation bugs.

What angers me the most is that I commonly see SPAs used for things that just perfectly fit the “traditional” model of multiple pages being separately requested and rendered. Surprisingly big part of the web consists just of text, images, video, and forms. That’s where most of the money is. We like to read news and blogs, watch cat pictures, buy stuff, and it’s nice being able to do most of our banking without having to leave my apartment. There are some really cool and sophisticated things out there that make good use of JavaScript – games, interactive maps, simulations (check out Fireflies). Your line-of-business web-app doesn’t need any of that.

Not only that, but also that projects often start as SPAs just because it’s the default approach in given org, without bothering to analyze if it makes any sense in the given context. And all that SPA-specific cruft is usually the first thing built. But hey, why waste time thinking what your users actually need if you can spend it re-implementing navigation, poorly?

BTW, I googled for other blog posts on the web that talk about SPAs. In ironic twist, the first result for my query, which seems to be a corporate blog, fails to show any text. The page loaded, but instead of the blog content, I see infinitely-scrolling loading circle. Why? Because some moron thought that a blog should be implemented as a SPA. You had one job – show me text – and you still managed to fail.

Against perfectionism

Perfectionism is often seen as a virtue in the software world, but it is a disease.

Perfectionism is a belief that anything less than perfect is unacceptable, and that we should avoid mistakes at all costs. It’s rejection of vulnerability. It results not only in misery, but also cancelled projects and bad products.

It’s not that striving to be outstanding is bad; it’s just foolish to believe that you can achieve great results without exposing yourself to failure. To make good product, you need to start with a small product, and let your users tell you which part of it suck.

Perfectionism kills projects. Often, the vision dreamt up by a perfectionist designer is not feasible to realize within a sensible timeframe, or at all. What’s worse, a perfectionist believes that the product they imagine would be great – but, arguably, it wouldn’t be. It has not been used; it could not benefit from feedback; you just can’t know if it is any good without having a good number of people trying it.

An an industry, we know that starting small and learning from feedback is the way to go – books praising this approach to product development are classics (think “Mythical Man Month”, “Lean Startup”), and “Agile” is the ultimate buzzword. We have known it for decades, but we don’t act like it. It’s still common for software projects to take multiple months before it is ready for the initial release. It’s even more common to fool ourselves that it serves the users better than iterative approach.

Perfectionism can hurt individual programmers in similar way. When we hide from critique, we can’t grow. When we skip the simplest approach that could work and instead immediately reach for overcomplicated ones, we miss the point. It strokes our egos, it makes us look better for other programmers, but It’s not about us; it’s about the users.

There is also the opposite problem – there are plenty of people who don’t give a shit about quality. But that is another story.

Code reviews

I have mixed feelings about code reviews.

On one hand, they can help with spotting mistakes and improving design. Another perspective can improve code, I totally get that.

On another hand, code reviews are disruptive roadblocks in my (and everybody else’s) workflow.

Here’s a common scenario:

I make a change to solve a problem (let’s call it A), create a pull request, wait for the build pipeline to succeed, and ask for a review.
Then I have to wait for the review, so I switch context to do some other task (let’s call it B).
The reviewer probably wasn’t just sitting there doing nothing; they also get disrupted by the review and need to switch context from whatever they were doing. Not great.
While I just focused on the problem B, I got pinged by the reviewer that he approved A. But something else just got merged into master and I have to rebase and solve some conflicts. So I need to switch completely from B back to A. Again, not great.
I solve the conflicts, wait for the pipeline, switch back to B while the pipeline runs, then ping the reviewer, then work on B again while waiting for the review, then I get pinged, and finally I merge A. Ooff.
So now I can switch for good to the task B. I realize that I have to rebase after A got merged, and have some more conflicts to solve. Finally, I create a pull request… and the whole cycle repeats.

Of course, often there are more roadblocks: some major changes are requested by the reviewer; then the reviewer is not accessible, so you need to chase somebody else, and they have their own opinions about what you should change; the flaky pipeline makes you re-trigger it half the time, adding more context switching.

It’s horrible. From what I see in most teams, people get used to this kind of flow and no longer realize how disruptive it is.

But I can’t. I just can’t stop imagining how much more could be done with more efficient process.

What are the alternatives?


Almost every time somebody mentions growth in business context, they mean increasing the number of employees. Why?

It seems to be a pretty arbitrary dimension, especially taking into account that headcount isn’t the primary purpose of business. Making money is (at least in for-profits)!

There are big downsides of growing headcount:

  • You have to pay more in salaries
  • You get less efficient
  • You need more coordination
  • The company gets (more) hierarchical, with people at the bottom getting further from the vision, and managers on the top further from the actual work getting done.
  • You have more work in progress (which is bad) and it’s harder to change direction

There are other ways to improve the business that are less prominent:

  • Improve efficiency
  • Increase focus: drop your worst customers and products.
  • Make people that already work for the company more happy, efficient and loyal.


Checklists are powerful. They let you off-load some mental effort into easily-accessible text. They prevent bad things from happening and they help you to remember about the good stuff.

You might think your work is too special to be described by a checklist, but consider two facts:

  • A checklist doesn’t need to reflect the task completely: it just needs to mention elements that are commonly missed, or have particularly catastrophic consequences when missed.
  • There probably are parts of your work that are more repeatable than you think, yet cannot be easily automated.

Here’s an example of checklist I use: pull request checklist.

- [ ] Did I look for parts that could be in an independent, smaller PR?
  - WHY?  To make it easy to review, and also easy to revert without unexpected consequences.
- [ ] Did I test the code?
  - WHY?  To not waste time of the reviewer.
- [ ] Did I make sure that there is no commented-out code or other debugging artifacts?
  - WHY?  Because it should never make it to production, though it sometimes happens.
- [ ] Did I comment where needed?
  - WHY?  So the system is understandable even when code alone is not expressive enough.
- [ ] (For visual changes) Did I attach a screenshot or GIF?
  - WHY?  To make it immediately obvious to the reviewer what changed.

The “WHY?” section helps me remember the intention behind each item, and makes it more accessible to others if I would share it. It also helps me to judge whether an item is still relevant; perhaps the core problem has been addressed in other way in the meantime – e.g., an automatic check for commented-out code was added as a git hook. Some items are optional or context-dependent, as the last item on this list.

Some other contexts where checklist can be useful:

  • Starting a new project
  • Job interview preparation (from either side)
  • Bug reporting
  • Git commit message & content
  • Writing an essay

If you perform given task everyday or almost everyday, it might be enough to just review the checklist from time to time (once a month, for example). If you do that task less often, use the checklist every time.

Comfort vs focus

I like to have an ergonomic keyboard at my desk, multiple big displays and all that. But since I started using a tablet with a little keyboard, I noticed that most of my side project work happens there, not on my fancy home workspace; even when I’m at home.

It doesn’t make sense at first – my “big” workspace is better in every dimension: there is this fancy ergonomic, mechanical keyboard; on iPad I have shitty rubber keys with layout I’m less used to. Huge screen can fit several terminal windows and a browser, compared to 10 inch tablet screen that fits a small browser window and one or two open files in the terminal, at best.

But there is another force at work here. Minimal workspace brings constraints, and that helps me to focus. While the big workspace is objectively more comfortable physically, the smaller is sometimes more comfortable psychologically when I’m not at my calmest state.

Sometimes, more comfort accidentally leads to less focus.