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.
- Write code that is sensibly formatted and readable.
- Place your code in a directory structure that is sensible and
standard for the implementation language used.
- Follow agreed upon project conventions.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
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.
-
Test first, code second. Don't code a solution until you've demonstrated
the problem(s) that need solving.
-
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.
-
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.
-
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.
-
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.
-
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.
-
"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.
-
"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.
-
"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.
-
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.
-
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.