Principles can be checked by the static Principles class. Each method requires at least the dependency Model as Argument. Unlike the Verifier class none of the principles deliberately throws an exception. They return collections of detected violations instead.I choose this behavior, since you generally want to refine the results by your own rules. Each of the methods searches for violating candidates among the whole solution (modules where the property IsPartOfProject is true), because most of them require information of the whole project in order to determine, whether a single entity contradicts with the rules. Under some circumstances you might allow being relaxed with principles. You have assemblies that deal with arithmetics, others with network communication or user interface. I leave it up to your experience to decide, if you treat them equals.

Acyclic Dependencies Principle (ADP)

The dependency graph of packages (modules and submodules) must have no cycles. Cycles increase the work to re-build and eventually make every package depend on every other package. Return strongly connected subgraphs in the dependency graph of submodules. Those describe cycles. Cyclic dependencies on module level are already covered by your compiler.

The principle analyzes the graph of submodules. Cycles between modules of course are valiated by your compiler. When you have to press F6 / CTRL+SHIFT+B twice per build, then you know where it comes from. On the other hand detecting cycles between classes is irrelevant in terms of software architecture. The package dependencies should describe an acyclic directed graph, while classes inside a package should work together and should change together, check for Relational Cohesion RC or Association Between Classes ABC metrics.

Don’t Repeat Yourself (DRY)

Repeated code unnecessarily inflates the code base. It may also hide the real purpose of a method since it then fulfills multiple tasks. Even worse is the impact on maintainability. Changes to one occurence are not transferred to all other places. The code base ends up with slightly diverging implementations of the same logic.
 Duplicated code inside the same class or in sibling classes can often be solved by extracting a method. Duplication in unrelated classes is candidate for the extract class refactoring. The method determines possible duplications by comparing the instructions of each method. There are usually many more instructions than lines of code. A threshold for minimalLength to query is about 40.

Law of Demeter (LoD)

The law of Demeter limits the interaction between classes in a system. It supports loose coupling of components since access to distant classes is prohibited.
A method of an object is allowed to invoke the following kinds of objects:

  • members the current class
  • members of its parameters
  • methods of associated classes (e.g. the return value of a method of the current class)
  • global variables (all accessible static members)

In general, call chains like "object.Something().Foo().Bar()" are forbidden. However, it is allowed to access members of data objects in a chain as long as the chain starts with a data object. Builder pattern is allowed, too.
Allowed:

  • dataObject.MemberOfDO.AnotherMemberOfDO...
  • classObject.Member (stop)
  • MemberOfCurrentObject().AnotherMemberOfCurrent()...
  • builder.Build(x).Build(y).Build(z)...

Forbidden:

  • classObject.Member.Member
  • classObject.MemberReturnsDO().MemberOfDO
  • classObject.Member(parameters).AnotherMember(parameters)

 

Liskov Substitution Principle (LSP)

When the type Tchild is derived from Tparent, then any behavioral assumption that can be made about an object of the mutable type Tparent must also hold for objects of the derived type Tchild. Otherwise these types cannot be exchanged without altering the context they live in.

DependencyAnalysis implements a validation for LSP of members and types. For types so far, the ruleset is pretty simple. The rules might be extended over time:

  • Rules for variance are already covered by the compiler, so they are not explicitely checked
  • At least one overridden member violates the LSP 
  • Constructors count as overrides

An overridden member obviously violates the Liskov Substitution Principle when one of the following applies:

  • The member does not call the base member, given that it is concrete rather than interface or abstract. 
  • The method returns before calling the concrete base member.
  • It throws an exception type not assignable to exceptions from base member of any inherited type. 
  • There is a conditional branch on input parameters with return or throw statement 
  • The member throws an exception before calling the concrete base member.
  • Fields declared in base types are modified. Modifications to state of the current type are allowed though, as they are extensions not modifications of the supertype. 
  • The member calls to a state changing method of a base type. 
  • The return value of a method is modified, properties of reference types are allowed to be touched though

 

Methodenreinheit (Purity of Methods)

Definition

Methodenreinheit is term from mathematics, which describes the preference for solutions that do not introduce foreign concepts to solve a problem. For a programmers solution a foreign concept is the change of something outside the method. A method is considered pure, when its execution has no recognizable side effecst on its environment.

When should a method be implemented pure?

Whenever reproducability is a requirement for a methods outcome you should consider the effects a method has on the whole program. Calling multiple times. Conversions of course must be pure. So should any method that is supposed to retrieve data, while it does not imply setting some state. Especially, this is true for an exposed API, where the caller is not familiar with the internals of your code. Callers rely on you following the principle of command-query-separation.

Code contracts are one use case, which enforce you to manually annotate method with the PureAttribute, though they do not check validity of an annotation.

Before parallelizing tasks you should be aware of sections that can influence other threads. With pure methods you are enabled to eliminate critical sections.

Common coupling is poison for automated tests, methods modifying commons (static members, global variables), while others are reading them. You sometime hear things like: “These tests are sometimes red, there is an issue with the order they are executed.” In fact the order is not the issue. Executing them in random order reveals the issue. The tests either are not isolated from each other or they cannot be isolated due to coupling resulting from impure methods.

Rules

The enumeration Purity is a set of flags, where Pure is zero. Any method can be marked with multiple flags, which define different reasons why the method is not pure. Only methods of the current project are considered. Methods in framework or foreign modules are not analyzed.

Allowed for Purity.Pure:

  • The method modifies an object it instantiates
  • The method modifies its return value
  • Call pure methods
  • Call methods that modify instantiated objects
  • Call methods that modify the return value

Nothing of the following must be true:

  • Set a static field, property, array element, static event = Purity.ModifyCommon
  • Call Add, Insert, Remove, RemoveAt on static collection = Purity.ModifyCommon
  • Call a method that sets a static field (applies recursively) = Purity.CallModifyCommon
  • Modify the current objects state or the state of a member (property, array element, event, modify member collection) = Purity.ModifySelf
  • Call a method of the current object that modifies the current object (applies recursively) = Purity.CallModifySelf
  • Call a method of a member that is marked with Purity.ModifySelf or Purity.CallModifySelf = Purity.CallModifySelf
  • Change the state of a parameter = Purity.ModifyParameter
  • Call a method of a parameter that is marked with Purity.ModifyCommon, Purity.CallModifyCommon, Purity.ModifySelf or Purity.CallModifySelf = Purity.CallModifyParameter
  • Call a method with a paramter that takes a parameter of the current method or the current object and that is marked with Purity.ModifyParameter or Purity.CallModifyParameter = Purity.CallModifyParameter

When Methodenreinheit is called for a member other than a method then the result will be Purity.Undefined.

Note: The principle method EvaluateMethodenreinheit determines the purity value for all methods in the current project. By default the value of MemberDescriptor.Purity is initialized with Undefined. Only after executing Methodenreinheit the property is set.

Stable Dependencies Principle (SDP)

The dependencies between packages should be in the direction of the stability of the packages. A package should only depend upon packages that are more stable than it is. Stability describes the likelihood for a package of not changing.
Find any dependencies between modules or between submodules, where the target of the dependenc is less stable than the source.

 

Egg and Gherkin is a development tool written in C# to qualify the evolvement of your software architecture within predefined limits. Controlled by the unit test framework of your choice, it gives immediate feedback when breaking architectural constraints.

Last edited Dec 26, 2013 at 12:30 PM by The_eg, version 23

Comments

No comments yet.