Principles in software engineering are not usually rules of the form Thou shalt … or Thou shalt not ….

Software design decisions are rarely justified by saying "Its a matter of principle".

A principle's most important role is determining what you learn from experience — how you understand cause and effect relationships that deal with values.

So, software engineering principles focus your attention on crucial issues:

Separation of concerns is a recognition of the need for human beings to work within a limited context.

As described by G. A. Miller [Miller56], the human mind is limited to dealing with approximately seven units of data at a time.

A unit is something that a person has learned to deal with as a whole — a single abstraction or concept.

By separating software abstractions, the components that implement them are easier to use and maintain.

Separation of concerns is closely related to the concepts of cohesion and coupling.

Taking a broad view,

Separation of concerns is a broad principle with many sub-principles depending on the nature of the separation.

The principle of modularity is a specialization of the principle of separation of concerns.

Modularity implies separating software into components according to:

Modularity is related to the software developer's value of cohesion, or making sure that a component's responsibility centers around a single unifying abstraction.

Parnas [Parnas72] wrote one of the earliest papers discussing the considerations involved in modularization.

A more recent work, [WWW90], describes a responsibility-driven methodology for modularization in an object-oriented context.

The principle of abstraction is another specialization of the principle of separation of concerns.

Abstraction requires:

Failure to separate behavior from implementation is a common cause of unnecessary coupling.

For example, it is common in recursive algorithms to introduce extra parameters to make the recursion work. Here is a Scheme procedure that can compute factorial:

	(define (factorial-product a b) ; compute a × b!
          (if (= b 0)
              a
              (factorial-product (* a b) (- b 1))))
      

To compute the factorial of n, one must call

	(factorial-product 1 n)
      

which exposes details of how the computation is done and couples the algorithm's use to its implementation.

To fix this, we define:

	(define (factorial n)
          (factorial-product 1 n))
      
Now to compute the factorial of n, one calls
	(factorial n)
      
and the implementation is free to change without clients knowing about it.

Design by contract is an important methodology for dealing with abstraction.

According to this methodology, software design is guided by the following specifications:

The basic ideas of design by contract are sketched by Fowler and Scott [FS97].

The most complete treatment of the methodology is given by Meyer [Meyer92a].

Software design often involves two concerns that are often unconsciously tied together: what gets done and when it gets done.

There are numerous design problems where these two concerns need to be separated.

Failure to separate these concerns results in what can be called temporal coupling.

Many of the program design methodologies and techniques in use today — data structures and design patterns for frameworks, toolkits, and asynchronous programs — are aimed at addressing these difficulties.

Software engineers must deal with complex value relationships in attempting to optimize the quality of a product.

For example, timely delivery (a purchaser value) may conflict with response time (a user value).

Thus it makes sense to separate handling of different values by:

Efficiency is often dealt with as a separate concern in an optimization step:

Complex applications often have several different types of components. For example, a complex web application may involve

A well-designed application framework allows developers with knowledge of one type of component to work on that type of component with minimal need for skills involved in other types of components:
Focusing on anticipation of change is important for two reasons: The first reason is fairly obvious. The second requires some explanation.

Computer software automates a solution to a problem.

The problem arises in some context, or domain that is familiar to the users of the software.

Users of software within the domain see domain data differently than software developers.

Working out an automated solution to a problem is thus a learning experience for both software developers and their clients.

For developers:

For clients:

The principle of acticipation of change recognizes the complexity of the learning process for both software developers and their clients.

If the problem to be solved is complex then it is not reasonable to assume that the best solution will be worked out in a short period of time, but some developer values can help:

The principle of generality is important in designing software that is free from unnatural restrictions and limitations, and that survives beyond its expected lifetime.

Examples that embrace generality:

The principle of incremental development is embraced by the spiral model of software process, and later agile approaches.

In this process, you build the software in small increments; for example, adding one use case at a time.

Advantages:

The principle of consistency is a recognition of the fact that it is easier to do things in a familiar context.

Such contexts can be as diverse as coding style and idioms, graphical user interface design, and object-oriented library APIs.

Laying out code text in a consistent manner serves two purposes. At a higher level, consistency involves the development of idioms and patterns for dealing with common programming problems.
Consistency serves two purposes in designing graphical user interfaces.
As the object-oriented class libraries grow more and more complex it is essential that they be designed to present a consistent interface to the client: