If you are using the Opera web browser, use full screen mode to see the presentation in slide show format.
Location of presentation: http://www.carrotpatch.org/reference/software-arch-secrets/
Press "t" to toggle between slideshow mode and outline view. See "Controls for S5 presentations" for more controls.
This presentation covers key guiding software engineering principles for creating a maintainable, extensible design.
It is not a comprehensive guide to design principles. Notably, performance design principles are not covered.
Architecture
Merriam-Webster OnLine
Similar to traditional architecture, computer architecture is about how multiple components are built to interact with each other to form a cohesive system that can meet specific design objectives.
A software architect is responsible for guiding the overall design and evolution of a software system.
“Needs” include many aspects:
The importance of meeting the desired behaviour must be evaluated within the context of the project’s business objectives. A software architect must judge what future flexibility is not warranted, given what is known about the business environment, and choose to eliminate these paths. Choosing what to cut out can be more important that choosing what to keep.
From a process standpoint: software architects are responsible for approving High Level Designs.
Designers looking to get their designs approved should think about how they can make it as easy as possible for the architect to understand the proposed changes and any necessary background information.
For the purposes of this presentation, the following types of software work products are defined:
Every component of software should have a clearly defined role, which interacts with other components in clear ways. They are like characters in a story: each with their own purpose.
Fiction | Software |
---|---|
character | component |
story line | walkthrough |
character motivation | purpose for component |
character relationships | component interactions, message sequence charts |
Why do books / films / TV shows use archetypes and stereotypes? Until the characters / roles are established, readers / viewers have trouble following what is going on — they have not yet determined the appropriate frame of reference.
By using familiar characters, the author helps the audience get up to speed more quickly.
In the same way, by using familar software programming components, constructs, tools, libraries, and patterns, those who need to understand your code can do so more quickly.
Good stories introduce new aspects
of a
character’s personality
that are consistent with the character’s overall nature.
Bad stories introduce random facts on an “as
needed”
basis to suit the purposes of the plot.
Good software has clearly defined
components with
specific roles. As
changes are made, they are integrated into the existing roles.
Bad software has many bits of state here and there
to meet the needs
of today’s new feature or bug fix — the components lack cohesion.
Key challenge: how to define the roles in a way that is useful on all levels (e.g. class, component, subsystem, process).
A key problem in software design is how to scale the idea of roles. Each piece of software has its own specific role, but since humans cannot think about hundreds or thousands of roles in their mind at once, these roles must be composed into higher level roles. A software architect must think of a way to design the roles so they make sense on a small, medium, and large scale.
Design components so they can be enhanced and extended independently from each other.
Each component must define a clear interface that fits with that component’s role. The interface might not be strictly procedural, although with traditional procedural-based languages, this is the easiest way to have strong enforcement of the interface. Other components must only interact with the component using the documented interface.
Having a documented interface makes it easier to change the internals of a component, and to reuse the component in new ways.
A component should not have needless dependencies, including implicit dependencies where it has detailed knowledge of the implementation of other components, as this increases maintenance costs. However, it should use other components rather than clone their behaviour and knowledge.
Note the tradeoffs for minimizing your dependencies are a bit different if you are designing a software library versus a software application. Although the basic principle is still the same, a standalone software library may need to put a higher priority on avoiding using other components. A software library that is part of a software platform, on the other hand, can take advantage of this and reuse platform components.
Software components are open for extension, but closed for modification.
The Open/Closed Principle was first used in Bertrand Meyer’s 1988 book, Object-Oriented Software Construction. It was redefined by Robert C. Martin in the January 1996 issue of the C++ Report to refer to the inheritance of interfaces which are “closed” (having been defined in the parent class), and having new implementations added in new subclasses.
The intent is to allow new features to be added by adding new software, without having to change the existing software. Following this principle leads to designs where separate features are decoupled from each other and implemented with encapsulated components.
Note bug fixes of course require modification of existing software. Enhancements which meet the following might also be suitable for keeping within existing components rather than creating new ones:
However, components can only be extended at its provided extension points. If no suitable points exist, then the base framework must be modified, either to provide the right points, or by reworking the logic to handle the new requirement. This may require recasting the roles of the components to fit the program's new needs.
Finding the right balance of extensibility is a challenge (see Principle 6).
Challenge with data-driven algorithms: how is the data managed and configured? How is it validated and checked for consistency?
Desiging software is like designing a murder mystery: who knows what, and when?
Programming can be considered to be about managing flows of data and processing them. The lifetime of information is an important aspect of a software design, seen from a data-centric point of view.
“information” includes both the data that a program is processing, as well as data, algorithms, mappings, strategies, and so forth used by a program to process the data.
Note in an object-oriented design, similar decisions must be made for the lifetime of objects. Typically, their lifetime will mirror the lifetime of some form of information required by the software.
Managing the lifetime of information is key to keeping components encapsulated from each other. Without clear information ownership and management policies, it is easy for information to leak across multiple components.
Designing the ownership policies for information is a useful step in avoiding circular dependencies, since knowing the component responsible for each bit of information also identifies the dependencies between components for each bit of information. The ownership policies also help avoid upwards coupling of components, where a lower level component is dependent on an upper level component.
The lifetime of a variable should match the required lifetime of the information it holds. For example, if an object only needs to be used within a method, then it should be held in a local variable, rather than a data member.
Have a single owner for each piece of information who will be responsible for it.
Organize components into different levels of abstraction. As a general rule, components should not bypass intermediate levels within a hierarchy.
Parameters to a function should be roughly at the same level of abstraction.
Performance issues may dictate a need to bypass levels.
Note this guideline is flexible and you must use your best judgment. There are cases where it is more useful for a component to interact with many different levels. For example, some components might provide general services that are needed at a variety of levels (e.g. logging functions).
Keeping a method/function’s parameters at the same level of abstraction helps maintain encapsulation. The user of the function will not have to track information across multiple levels of abstraction.
Having levels of abstraction limits the amount of interactions each component can have with others. This helps with defining clear roles for each component and in simplifying their story.
The benefits of adding additional extensibility must be weighed against the projected need for the additional capability, and the potential loss of encapsulation or cohesiveness. A component with a fuzzy purpose is harder to understand than one with a focused role; this ambiguity increases maintenance costs.
Use techniques to keep the hooks for extensibility well defined and controlled, such as strategy pattern, template method pattern.
Advice for architects:
Advice for architects:
Location of presentation: http://www.carrotpatch.org/reference/software-arch-secrets/