My main goal is to get to a simple design, that fulfils a user need, with a solution that is readable, well tested and extensible without being over-engineered.
I am in favor of designing for the ‘will’ happen and not for the ‘might’ happen. I’d rather not making assumptions about the future because I am not a good fortune teller. It feels better to find the right solution for the actual problem we are trying to solve.
I like the don’t make me think approach when facing the design of an application: both from the user and the development perspective. I aim for code that any developer, regardless of their experience, can understand. If you need a senior developer to understand the codebase, then you might need to pay some tech debt to decrease code complexity.
I believe that the architecture is the result of multiple iterations on top of a codebase while fulfilling user needs. In my opinion, the architecture arises and evolves alongside the project. Designing the architecture up-front could take you to situations where you need complex code to sustain very simple requirements, slowing down the team performance.
I enjoy Object Oriented Design, Test Driven Development (TDD), Behaviour Driven Development (BDD) and refactoring code. I truly believe that the TDD process need to be driven by finding the minimum amount of code that satisfies the test and using names that mimic the domain model of the problem you are trying to solve.
In my opinion a messy monolithic app won’t be fixed by porting into microservices. Indeed, you might get to a worst place. A safer path could be to decouple the components locally, check the dependencies and, if needed, extract low coupled services when there are strong reasons for it (for example, performance, or team size). And I would not advice using microsevices for startups.
I understand microservices as a step forward in a matured monolithic app, and not as an up-front solution. In general, I don’t favor them, but I agree that they fit quite well in big organisations. They feel to me more like an organisational pattern, rather than an architectural pattern for an specific application.
Code quality starts with choosing the right name for the right abstraction. Every single name is significant: methods, classes, variables, modules, folders, etc. For example, having the right folder structure in the codebase could prevent new developers in a team from browsing hundreds of files, and to get a better understanding on how the application is designed.
I believe that tests should be treated as production code; good tests are readable, explicit, and they describe the expected behaviour of the Subject Under Test. I am ok with code duplication in tests if the readability of a single test gets better. Sometimes, trying to find the one-line approach to describe a test might take you to a worst place. I also try to be cautious when using mocks and stubs; the main reason is the risk of mocking the implementation and not the dependencies.
I am not in favor of micro-frameworks (custom frameworks) within the codebase. I try to prevent my colleagues from spending several hours decrypting ‘configurable’ code, so I code based on standards: common patterns and obvious abstractions.
I think that code readability and code quality should lead the refactoring process, and it is more important than performance in the first approach to fulfilling a requirement. In my experience, bottlenecks are not related to our code, but to the way we interact with external services.