Showing posts with label design. Show all posts
Showing posts with label design. Show all posts

Tuesday, March 15, 2016

When to write setters

I have set out, almost unconsciously, to use constructor injection by default in the last few years while writing object-oriented applications. With Dependency Injection as a given, constructor injection satisfy most of my requirements for building an object graph and dynamically configuring collaborators.

The spectrum

I see the statefulness of an object not as an absolute but over a spectrum.
At one end of the spectrum we have immutable objects: these objects acquire a configuration in their constructor and are effectively final (to employ a Java-specific term) for the rest of their lifecycles. Their fields are private and there's no way of modifying them outside of the constructor; their collaborators are only scalars or other immutable objects. In Java fields can even be final so that accidental reassignment is regarded as a compile-time error.
The physical state of an object may still change without its external behavior being affected, like in the case of caching. I still consider this kind of white-box-immutable objects as immutable.
Stateful objects instead may have a behavior that changes as a result of its own state (or of its collaborators). You call some Command methods on it, and the response of further calls to Query methods change. Hopefully the Commands encapsulate some domain logic to constrain the state transition to a valid and modelled one.
At the extreme end of the spectrum we find setters: methods which only mutate the value of one or more fields, possibly skipping any validation, domain modeling or consistency check. The setters considered here are public methods, because their limited-scope versions do not provide the same violations.
If you want to write procedural code, setters proliferate (still it's probably easier to just use public fields at that point). There are only a few valid use cases where I have found setters useful in object-oriented programming, and here is the (short) list.

Configuration which has default values

Classes may have a few configuration values that you are able to tune; especially when there is more than a few of these parameters, I find useful to separate hard dependencies in the constructor and setters that are able to override the default parameters when the object has already been constructed. If you forgot to call these methods, the object still has to work correctly.
Alternative solutions for this use case are of course constructors with default parameters, which I definitely prefer if there are not so many options to tune (1 or 2). You can also look into with Value Objects which produce a new instance upon reconfiguration, and model all of the configuration parameters as a single entity; or into a Builder if you want to invest in an additional class and its API.

Adding Observers

Observers (or listeners if you prefer) are collaborators which are notified of internal events happening inside an object that may interest them.
I treat the observers of an object as an append-only list data structure, with an empty list or array as the natural Null Object. The object is initialized without any observer, and a setter like addListener(...) has the more limited capability of adding an observer but not of removing or modifying an existing one.
The nature of the Observer pattern is investing on a common bus that many observers can be attached to, even if they come from different packages and libraries. Therefore I find it natural to support the dynamic wiring of other objects, even if they make the object more mutable can before. The needs of integration become more important than guaranteeing safety of construction in these scenarios.

Reconstitution

When objects are unserialized from a cold storage such as a stream of bytes or a JSON object, encapsulation is very likely going to be violated. Object-relational mappers have been doing this for ages by working directly on annotated private fields, with sometimes powerful results but also lots of dangers from storage and code being out of sync.
In the scenarios where you control the reconstitution of objects, such as rebuilding an object from a MongoDB document, it's often easier to provide an explicit API like a setState() method than to rely on the magic of a library which is going to bypass your public methods. To constrast the possible misuse, you can tag this method as @private (or package protected in Java), or make it very awkward to use outside of the persistence context by requiring a particular data structure to be passed in.

Conclusion

There are very few use cases for setters in real object-oriented programming; default to constructor injection and to immutable objects to avoid overcomplicating your design. Employ setters for non-mandatory, cross-cutting initializations so that your code does not have to bend over backwards to support these use cases while at the same time it can be robust to cowboy modification of internal state.

Saturday, January 02, 2016

Building an application with modern Java technologies

Sometimes Java gets a bad rap from Agile software developers, who suspect it to be a legacy technology on par of Cobol. I understand that being the most used language on the planet means there must be projects of any kind out there, including lots of legacy. That said, Java and the JVM are a huge and alive ecosystem producing value in all kind of domains from banking to Android applications and scientific software.

So I built a sample Game Of Life with what I believe are modern Java tecnologies:
  • Java 8 with lambdas support
  • Gradle for build automation and dependency resolution (substitutes both Ant and Maven)
  • Jetty as an embedded server to respond to HTTP requests
  • Jersey for building the RESTful web service calculating new generations of a plane, using JAX-RS
  • Freemarker templating engine to build HTML
  • Log4j 2 for logging, encapsulated behind the interface slf4j.
On the testing side of things:
Here's the result:
The application is self-contained and can be compiled, tested and run without any IDE or previous machine setup except having a JDK 8 on your machine. See the project on Github for details.

Wednesday, April 23, 2014

Integrated tests are not feeling well. Long live design.

Take me down to the big Rails city / where the tests are green and they take 10 minutes
I checked the date this afternoon:
$ date
Wed Apr 23 14:38:08 CEST 2014

and apparently it's 2014, but there's still a widely held belief (in some circles) that integrated (or end-to-end) tests should be favored over unit tests. A belief that Test-Driven Development does not have a beneficial influence on the quality of your tests and code.
So today I'm repeating a few things I have been writing about in the last years.
Don't be too proud of this technological terror you've constructed. The ability to INSERT a row is insignificant next to the power of Domain Models.

A few properties of unit tests

Unit tests target one or a few objects at a time, without accessing different resources than the CPU and memory of the current process. With respect to integrated tests, they are:
  • Easier to write: their setup phase takes a few lines where Test Doubles are injected in a constructor.
  • Faster: a 1000-test suite takes seconds to run.
  • Isolated: they cannot interfere with each other or with the global state of the process.
  • Repeatable: they always give the same result no matter the initial conditions.
  • Precise: they tell you which wire does not work instead that the car does not start.

Listen to your tests

How many asserts must a man write down / before you call him a man

That's not to say integrated tests are not useful: I have a talk coming up at phpDay in which I will explain how our Behat test suite work and the optimizations we made to keep it under 5 minutes. Some concerns where integration tests shine are making sure systems built by different teams work together, refactoring legacy code, and acceptance-level tests written in the customer's language.
However, the ratio of unit tests to integrated tests should be in the range of 10 to 1, or even 50 to 1. If there is a force at work in a project that pushes for more integrated tests than unit tests, you are falling into a vicious cycle where instead of writing:
assertEquals("1.00", new Money(100).toString());
you're writing, more often than not:
createSubscription();
assertEquals(
    "<span class="money">1.00</span>",
    findPriceTag(get("/subscription/" + id))
);
and promoting coupling between the Money, Subscription and PageTemplate objects.
The Listen to the tests principle tell us to take difficult-to-write integrated tests as a smell: a warning that we need to break the dependencies between objects to be able to reuse them, for example in isolated tests (lowering coupling); and move responsibilities around until objects respond to an interface with a small surface area (increasing cohesion).
The benefit of TDD is continuously applying these two forces in your codebase. Renouncing to it while favoring integrated tests is thinking you're able to do the same in your mind, for the rest of the life of your codebase. We test because we don't want to break features, such as being able to perform a payment; but we unit test because we don't want to impact non-functional concerns such as reuse and the ability to change.
Take me to the magic of the moment / on a glory night / where the objects of tomorrow dream away / in the wind of change

Resources

I'll stop short. Here's where you can know more about integrated and unit tests, explained by some of the best people in the field.
And finally the style of this post is inspired by Call me maybe: MongoDB.

Monday, July 26, 2010

From static class to real object, and then from inheritance to composition

I'm refactoring Scisr, an automated command line refactoring tool, because it uses static classes as collaborators for the various Operation objects (Rename File, Rename Class, ...). In my refactoring, the static classes are becoming objects one at the time, and I inject those objects into the Operation ones.

Because of the quantity of collaborators, I am at the point where constructors are taking 5-6-7 arguments. But by no means this is a failure of Dependency Injection.

Using static classes means hiding the dependencies under the carpet, because you'll never know which objects access a static class from the signature of its constructor or of its setters. You must dig and look for :: operators (in PHP; in Java, for [A-Z]{1}[a-z]+\. or something like that; I'm glad Rasmus used a different operator for calling static methods).

Thus this refactoring uncovered a new code smell: the Operation objects have too much dependencies. Probably this is because common functionalities were refactored in abstract base classes instead of intermediate objects. This solution has great limitations, because the collaborators of an abstract class still must pass from the subclass constructor.
Furthermore, abstract base classes are not orthogonal: you can only inherit from one of them. Different functionalities end up in the same superclass only for convenience and not for cohesion.
The next step will be changing the design from this:
to this:
Possibly with more than one Collaborator, one for each use case where a subset of the old collaborators Db... were injected together. With this solution I hope to eliminate the explosion of the constructors of Operation classes.

Friday, April 30, 2010

Zen-Driven Development, part 1

Inspired by Eric S. Raymond's Unix Koans.
 
Long time ago, a student wanted to learn Test-Driven Development. Thus, he went to the Great Master to learn the ways of test-first design.
The student said to the Master "How can I learn TDD, this new methodology that would fix my broken designs?"
The master answered "If you want answers from me, I cannot help."
But the student did not understand.

The day after that, the student came back to the Great Master.
He said "Master, I am humile and eager to learn. Can you tell me how can I employ Test-Driven Development in my projects? I have so much legacy code to tame... I want to be Agile."
And the Master "Again, I do not have answers."

The third day, the student came back discouraged.
"Master, I do not want answers. Just give me a little clue, since in three days I have not written a single line of code. I want to be a TDDer."
"Listen to me, for a great truth is being revealed to you, without any certification course."
The student looked at the Master impatiently. The Master continued:
"You told me you want to learn TDD. But TDD does not start working on a problem by producing its answer. It starts from defining the problem, posing a question. Only when there is a clear, formal question defined you can verify you have answered it when the time comes."
Then there was a bit of silence, as the student nodded in agreement.
"Are you able to write a wget clone by using TDD?" the Great Master asked.
"Of course no, master..."
"Then we get a red bar."
And then, the student was enlightened.

Wednesday, April 14, 2010

The class design checklist

Given the good reception of the TDD checklist, I've decided to put together a similar one with suggestions for the generic class and interface design. These entities are the basic artifacts of object-oriented programming, thus this checklist is used at a lower level than the TDD one.
The form, however, is the same - a list of questions which should be answered by a developer before committing a new code artifact. There are really no particular phases in which to apply these questions, though, like in the Red-Green-Refactor cycle, so I organized them by topic. Feel free to pose these questions to yourself or to a developer you're pairing with anytime you perceive a smell in the code.
Note that many static analysis tools would point out some of this issues, but I think it is better to tackle possible problems early on in the development cycle, at the very moment of the initial design. If you employ TDD, design is performed one step at the time thus leaving you free of aggressive refactoring on the newly introduced concepts: this process converges towards cohesive and loosely coupled classes, which are blueprints for a good object graph.

Naming
  • Does the class (or interface) name describe what an instance of this class (or interface) does? Usually a name or an adjective plus a noun are good for a class, while an adjective is more appropriate for an interface. Some interfaces have names composed by one or more nouns.
  • Does the name contain unnecessary implementation details? Interface and abstract classes should not contain any reference to a particular implementation, but you should analyze this issue in context. For instance, XmlParser is not correct if at least a possible parser implementations does not work with Xml, while for a family of Xml parsers that differ in performance is appropriate. In the same motif, class names should not contain private implementation details of the class which may change, only the class real special trait in respect to the other implementations (e.g. XmlParser, HtmlParser, YamlParser.)
  • Is the fully qualified name of the class or interface correct? (also known as: is this artifact placed in the right package or namespace?) You can make a guess based on the number of dependencies that this new artifact introduces.
  • Naming conventions and best practices are also valid for method names, parameters, local variables, inline comments.
  • Is the name consistent with the rest of the object model? Is it part of the Ubiquitous Language? The more public is the named entity (in the ascending order private, protected, [package where applicable,]  public, published), the more important is to get a valid name immediately.
  • Should the name be influenced by a standard Ubiquitous Language? This is usually true for design patterns implementation, where leveraging the role names communicates much about the code structure. Other examples of a standard Ubiquitous Language are framework and programming language conventions.
Structure
  • How many levels of indentation are there in your code? Supposing that the first level is dedicated to methods, other two levels are acceptable, with one (thus two in total) being the norm. Extract Method will help breaking up the complexity in different, orthogonal methods.
  • Have you inserted switch constructs, especially similar ones? This structure is usually a smell, which can be refactored with a State or Strategy pattern.
  • If-elseif chains or even if-else constructs, when repeated, are equivalent to switch, with the latter being its two-fold substitute.
  • Are there new operators mixed with business logic? This is a no-brainer.
  • Are there any controversial constructs in the code, such as the static keyword, or goto?
  • If the code artifact is a subclass, does it extend the right parent class? If it is an implementation, does it implement the right interface? Check the semantics and analyze the proposition An instance of [entity] is always an instance of [parent], too.
Length
  • May a class want to implement only part of this interface? Segregate it in different pieces as much as possible.
  • Is the class longer than the standard size for your project? The suggested length varies with the programming language and the particular application, but a long class may be the sign of an imminent Extract Class refactoring.
  • Is the class size of the same order of magnitude of other similar implementations?
  • How many characters are the most complicated lines long? You may want to introduce intermediate methods or data structures to keep such lines readable. A common rule of thumb is the 80 characters of the original text terminal.
  • Method length is also a useful metric. The rule of thumb is a method should be fully visible in a single screen, but this doesn't mean that with a 30'' LCD monitor you should write longer methods. The original screen was 25 lines high, but you may want to extend it a bit for practical purposes and refactor later: extracting local method is one of the simplest refactoring and it has a very limited impact on the rest of the codebase.
I hope you already had many of this questions in your mind while coding. As always, feel free to add new insights in the comments, I would be glad to learn new practices from you.

Monday, April 12, 2010

The dangers of Late Static Bindings

There's a lot of (justified) excitement about PHP 5.3 new features, such as the support of namespaces and anonymous functions. Though, some glittering capabilities of the language are definitely not gold: the goto statement is probably the most debated example, but also the long-awaited Late Static Bindings support is an hammer which may hurt your fingers...

Technically speaking, Late Static Bindings give you the possibility to implement an effective class hierarchy with static classes. You can already see that there are two dangerous words in this definition: static and hierarchy.
In the code sample, you can see that the pre-PHP 5.3 static methods resolution was not affected by subclassing, as self always targeted (and will continue to target) the original class it was written in. Therefore, the static keyword has been reused in the context of method calls, resolving to the class that makes the static call.
It's simpler to show it than to describe it:
<?php
class Base
{
    public static function getHelloText()
    {
        return 'Hello from Base!';
    }

    /**
     * 'self' is always resolved to the class it is
     * written in.
     */
    public static function helloWorld()
    {
        echo self::getHelloText(), "\n";
    }
}

class Subclass extends Base
{
    public static function getHelloText()
    {
        return 'Hello from Subclass!';
    }

    // helloWorld() is inherited
}

Subclass::helloWorld();
With Late Static Bindings:
<?php
class Base
{
    public static function getHelloText()
    {
        return 'Hello from Base!';
    }

    /**
     * 'self' is resolved at runtime (late binding)
     * to the class where the method is called.
     */
    public static function helloWorld()
    {
        echo static::getHelloText(), "\n";
    }
}

class Subclass extends Base
{
    public static function getHelloText()
    {
        return 'Hello from Subclass!';
    }

    // helloWorld() is inherited
}

Subclass::helloWorld();
An equivalent functionality is available with the method get_called_class(), which returns at runtime the name of the class a static method is called in. This was not possible at all before PHP 5.3 and discovering what class is called in a static method is the central point of LSB.

It's not a mistery that static methods should be used sparingly and for particular use cases, as they are a procedural solution. Moreover, inheritance is an overrated method of code reuse that favors implicit assumptions and exposures over explicit decisions. In this article we will compare two examples where Late Static Bindings support proves useful and where it is not.

Good example: abstract implementation of a Factory Method.
Let's suppose we have a small, two-level hierarchy of classes that emulate an enumerated type. If we have a static factory method on the base class, we can implement as Flyweights the whole hierarchy of objects with only one method:
<?php
/**
 * Every subclass will have a method getInstance()
 * that returns the singleton.
 */
class AbstractEnum
{
    private static $_instances = array();

    public static function getInstance()
    {
        $class = get_called_class();
        if (!isset(self::$_instances[$class])) {
            self::$_instances[$class] = new $class();
        }
        return self::$_instances[$class];
    }
}

class Subclass extends AbstractEnum {}

$subclass = Subclass::getInstance();
$otherSubclass = Subclass::getInstance();
var_dump($subclass === $otherSubclass);
This is only syntactic sugar for an hypothetical Base::getInstance('Class'), and the single objects must be mere ValueObjects or data container and have no behavior to mock out, since static methods are the death of testability.

Bad example: Active Record which does not evolve towards Repository
Suppose instead we have the classic ActiveRecord class we extend to produce a very simple object-relational mapper (most of the PHP ORMs are based on this pattern.) If we had a find() static method on the ActiveRecord class, with LSB we can now make it work on every subclass, so that to find our users we can call User::find($id).
This is the death of testability and good design however: how can we mock out this class from a service that uses it? We are stuck with a procedural, hardcoded User::find($id) call.
Instead, libraries such as Doctrine 2 has evolved towards the Repository pattern, extracting and managing a Repository object specific for User instances, which can be obtained and which we can call find() on. Moreover, we can inject it into any services.

What can we learn from these two examples? Static methods are often the sign of a missing concept in the design, something which would encapsulate a collection of objects of the same class, or their shared metadata. Introducing a new object, being it a Table Data Gateway or a Repository, makes this concept explicit and promotes it to a first-class object, which you can pass around and mock as you want.

Friday, April 09, 2010

Domain-Driven Design review

Domain-Driven Design: Tackling Complexity in the Heart of SoftwareDomain-Driven Design, by Eric Evans, is the masterpiece of the DDD movement. It introduced to the world the harvested experience of Evans in working on model-driven development on a variegate group of object-oriented projects, presenting it as a series of patterns and meta-patterns (such as the Ubiquitous Language), and ranging from the design of a single class to a large map of different applications.

A brief summary
DDD is a very dense book, and it is logically divided in four parts:
  • Putting the Domain Model to work, which covers the importance of the Ubiquitous Language and the centrality of the model of the application domain.
  • The building blocks describes the patterns used at the finer level, such as Entity, Value Object, Aggregate, Service, Factory and Repository. 
  • Refactoring towards deeper insight covers the continuos process of adapting the Domain Model to new insights and crunched knowledge, believing firmly that an hidden model exists and will be reached with a supple design. Modelling as described here is like applying an unification theory to software.
  • Strategic design introduces the techniques for large scale structure and separation of contexts, such as the famous Anticorruption Layer (although it is not the only communication medium between different Domain Models.)
In my opinion the third and fourth parts are the most difficult to digest, due to their abstractness (not everyone of us has worked at that scaling level) and the perceived distance from working code. In fact, the first two parts are rich in code samples, which gradually vanishes towards the end of the book while the view on object-oriented applications is taken to an higher level. Diagrams, both based on Uml and not, are preferred throughout the last chapters.

How to read it
Many people start reading DDD full of confidence, scheduling five chapters a day, only to struggle very quickly, usually in the second part, and abandone it. Even in my recently completed reading from cover to cover, I waited some time before starting the third part.
This book is intended as a series of patterns - notice the capital letters used for most of the names - those names are specific terms with a precise meaning. In the case of the Gang of Four book on design patterns, this made it hard to read cover to cover because of the lack of a common thread between the chapters.
But in this book's case, the main theme is the model-driven design practice that evolves from the finest level - the class and its methods - to BoundedContexts and Responsibility Layers. That said, the information contained is very dense and I have never read more than 15-20 pages on a given day. I suggest you do to the same or you risk burning out before reaching half of the book. Also taking notes is a mean that forces high focus of the content and may help.

Other resources
There is a lighter book freely available - Domain-Driven Design quickly - which promises to explain you DDD in 100 pages. Don't read it: when I tried, I understood a concept only as long as I have already read it on the original DDD book, and never learn anything on the new material. The original is 400-page long, but it is already condensed as much as possible.
A book that expands on the code samples and the design process is Applying Domain-Driven Design and Patterns, which is a good read to combine with DDD (in fact, it is 600-page long.) You may refer to it when you want more practical examples of a pattern treated in the original book.

In sum, this book is often cited as the DDD book, and in the future it may be considered the bible of real object-oriented development. If you have it on your shelf, I suggest you to read as much as you can beginning from the start, skipping only the fourth part if you do not ordinarily deal with large-scale applications.

Thursday, April 08, 2010

phpDay 2010 (plus discount code)

I will be at phpDay 2010, the Italian conference on PHP and related topics, as a speaker.
There are many talks in English from foreign speakers such as Fabien Potencier, but my talk is in Italian. It is scheduled for the last day of the conference, 15th May 2010.
http://www.phpday.it/session/architettura-e-testabilita
Talk page on joind.in
Architettura e testabilità: il design di un'applicazione può essere influenzato positivamente da diverse pratiche. La facilità di testing é condizione sufficiente per un architettura che garantisca semplice manutenzione e alta coesione dei componenti. Argomenti trattati: Dependency Injection, Law of Demeter, Design Pattern creazionali (Factory vs. Singleton), Api oneste.
If you are interested, you can book a seat here. I have a 20% discount code I can give you if you ask me via email, I'm not sure publishing it here is allowed.

Wednesday, March 10, 2010

Acceptance Test-Driven Development

I am halfway through reading Growing object-oriented software, guided by tests, a book that teaches Test-Driven Development in a Java environment. A review will come soon, since the process described in this work is really language-agnostic and interesting also for php developers.
However, the book's authors introduce a very productive practice, which consists in a double cycle of TDD:
  • a longer cycle, where you write acceptance (aka end-to-end) tests, deriving them from the user stories or formal requirements, and make them pass;
  • a shorter cycle contained in the first, which happens in the phase when an acceptance test is red: you write unit tests and make them pass until the related acceptance test does not fail anymore.

This approach is an implementation of Acceptance Test-Driven Development, and in particular makes you write several unit tests for every acceptance test (read for every feature) you want to add. Acceptance testing gives immediate feedback on the application's external qualities: simplicity of use and setup, consistency of the interface. At the same time, unit testing gives feedback on the internal qualities: decoupling, cohesion, encapsulation.
When I started employing the double cycle, getting in the zone suddenly became less difficult. The advantages of the TDD process were for the first time applied to the whole process, from the requirements formalization to the end of a feature's development:
  • test-first paradigm. By the end of the development phase, regression tests will be already in place, and the production code will be forced to be testable.
  • The definition of "done" is very clear (the acceptance test passes), and you are more likely to write only the mandatory code to get a green bar at the higher level.
  • measuring progress is easy: the number of acceptance tests that are satisfied (weighted by points). You can even write a set of acceptance tests for the whole iteration in advance and keep them in a separate suite, moving them in the main suite when they start to pass.
To be a bit more specific, the php technologies I use for the two cycles of development are Zend_Test for the acceptance tests suite and plain old PHPUnit test cases for the unit tests one.
Zend_Test is an extension to PHPUnit that lets me define a contract for the http interface of a Zend Framework application, assert redirects, check parts of the html response via css selectors, and even falsify request headers to simulate ajax requests. The unit tests usually have no dependencies on a particular infrastructure, so PHPUnit itself is a powerful enough tool to write them with.
By the way, triting an automated acceptance test suite is more difficult than writing unit tests, as there is more infrastructure that gets in the way and a large amount of details that may render the tests brittle. Fortunately Zend_Test takes care of almost all the infrastructure (aside from the database, which I reset in the setUp phase of test cases), and acceptance tests code can and should be refactored to avoid duplication of the implementation details. For instance, Css selectors used to assert on parts of the html response can be stored in constants with an expressive name, and the request creation can be wrapped in helper methods that are composed in actual test ones. Also custom made assertions are helpful in keeping the noise to a minimum.

I hope this technique will be useful for all test-infected developers. It certainly enhanced my productivity and will to get a green bar. :)

Thursday, February 25, 2010

Levels of architecture

As I said in a recent post, it is important to have a 50,000 feet view of a software system in our mind before editing even a single line in its codebase. Without a big picture in mind, our changes can push the project in the wrong direction. But there are different views, each at a different level of detail, that may affect a software's design process.
A simple analysis produces this scale of units, ordered by increasing size. Every unit is composed by a set of units from the underlying level, which comes before it in the list:
  • The simplest unit of software is obviously the line of code.
  • The next smaller unit in object-oriented software is the method, being it public, private or with any access control policy.
  • The class and its specializations: Entity, Value Object, Service, Repository...
  • The package alias namespace: its purpose is to simplify references between items contained in the same unit.
  • The module alias component, which often presents a Facade to simplify its access.
  • The application alias BoundedContext. Different applications can work together and communicate via published protocols, anti-corruption layers, RESTful services, relational databases... There are no limits to collaboration paradigms.
The interesting part is that there are some metrics and rules of thumb that works at every level of detail in an architecture, while others gain greater effectiveness the more you are near (or far from) one end of the spectrum. Employing metrics and practices at the particular level of the architecture they are thought for is crucial.
Let's consider some general rules first:
  • number of units: programmers have limited Ram. People normally can work on 5 to 9 units at the time (obviously more when they are very similar or dumb), so a container unit should not be composed of an high number of contained units. For example generally a class should not present 300+ methods, and a package should not contain one hundred classes.
  • low coupling, high cohesion, information hiding: these concepts should be enforced at every level; one unit dependent on another is sufficient to transitively establish a dependency between the respective container units.
 And then the ones that change with the architecture level considered:
  • TDD and refactoring: applied at the low end of the spectrum. It is simple to refactor a private method, but it's very difficult to refactor a published protocol between two applications. 
  • the converse situation applies at the high end of the spectrum: thinking of a good api and refining the Ubiquitous Language is very important  because of the resistance to change of these kinds of units.
  • choice of the system under test: testing the single classes and methods is the preferred approach in a largest part of a project's test suite, because of the reduced number of test cases necessary and the resulting design aid (applied at the low end.)
  • code coverage is instead only significant at an high level. Some dumb classes may not have unit tests at all but at the same time they could be indirectly exercised by other tests. Similarly many static analysis metrics are meaningful if measured on a whole project.
  • Uml diagrams must be kept in sync with code, so they really add value when they are used at the boundaries of components or without much detail in them.
Feel free to add other practices you follow with preference at one end of the spectrum of units, or at every level of detail.

Featured post

A map metaphor for architectural diagrams

It is a (two-dimension) representation of a pipe. The map is not the territory , but in software engineering terms they are models of it....

Popular posts