Clean Code

Russell Bateman
last update:

Some notes on writing clean code, some of which might be "sacrificed" depending on a variety of factors (management mandate, tool choices, etc.). These have been variously called, "clean-architecture principles." Some of this is scraped from articles. Numbering is insignificant, provided only for convenience.

  1. Write code that is sensibly formatted and readable.
  2. Place your code in a directory structure that is sensible and standard for the implementation language used.
  3. Follow agreed upon project conventions.
  4. The Dependency Rule. At the heart of clean architecture is the Dependency Rule. It mandates that source code dependencies should always point inward. This inward directionality ensures a resilient foundation, emphasizing the separation of concerns and fostering a more maintainable structure. Tools like NDepend aren't just handy; they're essential for developers keen on visual checks and balances.
  5. Decouple frameworks. In the dynamic world of programming, it's tempting to intertwine business logic with framework-specific code. However, true brilliance lies in maintaining separation. For instance, while using Jersey for ReSTful web services in Java or ASP.NET core web API, always keep a protective layer between your core code and the entity framework.
  6. Entities first. Before you even think of databases or frameworks, it's crucial to focus on business rules. By honing in on entities initially, you're guarding your software against the pitfalls of tight coupling. This prioritization assures that business logic remains independent, versatile and agile.
  7. Databases as external details. A hallmark of a seasoned developer is their ability to treat databases, frameworks and third-party libraries as mere external details. This perspective ensures that the core business logic remains consistent and unperturbed, irrespective of external changes or upgrades.
  8. Database agnosticism. Your software should be a chameleon, adapting to whichever database environment it finds itself in, be it SQL, NoSQL, or even flat files. This adaptability ensures unparalleled flexibility, easy maintainability and scalability tailored to any project's unique needs.
  9. Leverage data-transfer objects (DTOs). DTOs are the unsung heroes of software architecture. They play a pivotal role in ensuring data moves seamlessly across layers without any unnecessary entanglement of business logic.
  10. Beware of large classes. Large classes are more than just unwieldy; they're often a sign of underlying design flaws. An expansive class is a ticking time bomb, prone to errors and complications. It's imperative to be proactive, splitting such classes and ensuring clarity of purpose for each segment.
  11. Shun global state. The allure of global states is undeniable, but so are the tight coupling and unpredictability they introduce. Instead of succumbing to their apparent convenience, opt for explicit dependency passing, ensuring more structured and reliable code.
  12. Prioritize configurability. In a constantly evolving tech landscape, adaptability is key. By externalizing configuration details and leveraging features like .NET Core’s built-in configuration system, you're not just adding layers of flexibility; you're future-proofing your application.
  13. Unit testing. Beyond mere validation, unit testing is a testament to the health of every application layer. It's the safety net every developer needs, ensuring that core components interact harmoniously without unexpected hiccups.
  14. Test first, code second. Don't code a solution until you've demonstrated the problem(s) that need solving.
  15. Clarity over shortcuts. The siren song of shortcuts can be tempting. But clarity and readability should always trump brevity. This focus ensures that, whether it's you revisiting the code or a new team member diving in, the experience is smooth and intuitive.
  16. Consistency in naming. The power of consistent naming conventions can't be overstated. This uniformity serves as a roadmap, guiding developers through the code making troubleshooting and enhancements a breeze.
  17. Maintain clear boundaries. A well-defined boundary acts as a fortress, protecting the core logic from external influences, be it user interfaces, databases, or external services. This clear delineation fosters modularity, a cornerstone of efficient software architecture.
  18. Embrace immutable data structures. Immutable data structures are akin to a trusted shield, guarding against inadvertent errors and ensuring predictability. Their adoption can dramatically reduce bugs and ensure a more stable code environment.
  19. Dependency injection. DI is a game-changer. It inverts dependencies, ushering in enhanced modularity and testability. By decoupling components and making them interchangeable, DI empowers developers with unmatched flexibility.
  20. "Don't repeat yourself." Repetition is the antithesis of efficiency. By adhering to this principle, developers can centralize, reduce and reuse code, streamlining processes and ensuring a harmonious software ecosystem.
  21. "Keep it simple, stupid." Complexity is the enemy of efficiency. By keeping architectures simple and straightforward, developers can ensure that they're building on a solid, easily understandable foundation.
  22. "You aren't going to need it." Predicting future needs can lead to over-engineering and technical debt. This principle is a reminder to build for the present, ensuring lean, purpose-driven code.
  23. Document decisions. A well-documented architectural decision is a lifeline for both current team members and future onboardees. By maintaining a comprehensive decision log, the rationale behind choices becomes clear, paving the way for informed future modifications.
  24. Limit method/function parameters. Simplicity should permeate every aspect of architecture, including function design. Limiting parameters to a maximum of three or four ensures readability and prevents overwhelming complexity. Overloading functions with countless parameters not only confounds developers but can also introduce unnecessary dependencies and increase the potential for errors.