Writing code

How to write code with a pragmatic approach. I highlighted important stuff with red and orange:

  1. Write the “dirty solution” and test it locally; repeat until all your ad-hoc tests pass.
  2. Refactor it to improve code quality:
    • Ensure class, variable, and function names are properly chosen.
    • Extract common code in shared methods and classes.
      • Separate layers, whenever applicable.
        • Ensure domain logic is separated from detail implementations (data access, user interface, external systems).
    • Make code as readable as possible. Both for other developers and for yourself!
      • Reduce the number of lines when possible, such as using LINQ instead of procedural code.
    • Make objects dependency injectable and therefore, unit testable.
  3. Write unit tests covering as much of the logic as possible.
    • Write all unit test names first, then implement them one by one.

By reading the unit test names, one should understand what your code does!

    • Use mocks to ensure proper isolation. Use a mock framework to save time.
      • Do verify calls from root services to dependency services. Remember that unit tests is also protection against breaking things by mistake, later.
      • Further extract private methods in main code into separate services to be able to write less or more simple unit tests.
        • The opposite is also applicable sometimes: you can, in certain conditions, decide to test multiple coupled classes as a single unit.
    • Of course, ensure all unit tests pass, and configure a continuous integration build on the server side to run them upon every commit, when applicable.
  1. Write integration tests only if really needed. From my own experience with type-safe OOP-enabled languages, in most cases integration is properly ensured through interface contracts between services themselves. You need integration tests only when integration issues may occur through extensions of those interfaces.
  2. Write automated tests that verify the common functional flows that your objects support, using a separate environment (database, execution queues, processes, etc.)
    • Do take the time to prepare that separate environment, even if it’s painful.
    • Sometimes to write functional tests you can use the same development tools as you use for unit tests.
    • Each test flow need to include an arrange phase initializing the environment state, just like you do the set up in unit tests, then act and assert results.
      • For preparation and checking phases you should leverage the libraries also used as infrastructure for the main code, not the tested services.
    • However, ensure that when a test completes, the environment is fully cleaned up, even in case of a test failure (this is usually not needed for unit tests).
  3. Add code comments to clarify roles of objects and their functionality.
    • Using comments you may be able later also to generate documentation for other developers, especially if you develop frameworks or APIs.
  4. Run code analysis and resolve all issues it indicates.
    • Sometimes, especially for infrastructure code, it’s fine to dismiss some warnings, but do include a clear comment on why that happened.
  5. Give your code to somebody else for review and obtain as many feedback as possible. If a flaw is found and resolved before releasing, it hasn’t actually existed!