What helps is the relative simplicity of the AVR architecture and its instruction set, and relatively large number of registers. So code usually isn't excessively long and it's quite linear.
But deducing the programmer's intent from the disassembly is very tedious and time-consuming because you are left without any high-level code structures, variable and function names, let alone comments.
If the disassembler can automatically detect IO port memory addresses and replace them with the datasheet names (like USART1_CR and so on), it will help, but you can search&replace it manually. Still, it's hard to see why the programmer reads memory from address 1234, increases it by 1, then stores back to address 1234; the original code might have had: millisecond_counter++;