LS2N — Nantes Université
Component Internal Design
Gerson Sunyé gerson.sunye@univ-nantes.fr
Introduction
Guiding Principles
Best Practices
Case Studies
Conclusion
The detailed design specifies the internal structure and behavior of components.

Create a model that can be translated to code effortlessly.


Designing software means making choices
This choices are often driven by software quality factors:
extensibility, performance, portability, testability, etc.
is the ability of a software to allow and accept significant extension of its functionalities without major changes in its design.
An extensible design:
is open to changes.
addresses a class of problems instead of a single problem.
does not contain immature/fuzzy concepts.
is the degree to which a system or component accomplishes its designated functions within given constraints regarding processing time and throughput rate.
is the capability of the software product to provide appropriate performance, relative to the amount of resources used under stated conditions.
Implement correctly at first, then measure performance and optimize it, if necessary.
Maintain consistency between different representations:
Verify that the optimized version is equivalent to the non-optimized one.
When necessary, use different programming paradigms:
Create interfaces to separate the two paradigms.
Do not mix paradigms: do not pollute one side with other side concepts.
Examples: Swig, JNI, .NET P/Invoke, Protocol Buffers (Protobuf), Apache Thrift.
First Rule of Program Optimization:
Don’t do it!
Second Rule of Program Optimization (for experts only!):
Don’t do it yet!
Third Rule of Code Optimization:
Profile first.
Portability is the ease with which the software product can be transferred from one hardware or software environment to another.
Levels of portability:
the software is adapted in its source-level, then recompiled for the new target environment.
the software is compiled into intermediate form (byte code), and executed on platform-specific virtual machines.
This the software is ported directly in its executable form, usually with little adaptation.
Control the interfaces:
identify all interfaces to the environment, and cast them in a standard form wherever possible.
Isolate dependencies:
recognize the portions of a software unit which must be adapted, and isolate these portions.
Think portable:
the designer must be constantly aware of his part of the likelihood of future porting, and the impact on portability of all design decisions.
Portability is often treated at code level. Some examples:
Java, XUL (XML User Interface Language).
Posix (Portable Operating System Interface).
CORBA (Common Object Request Broker Architecture), etc.
Limits:
Same user interface for different graphical environments.
No integration with other software (calendar, address book, etc.)
Develop different version of the same component.
For instance, a GUI for: KDE, Gnome, Windows, etc.
Testability is the capability of a software product to enable modified software to be tested.
Factors that influence testability:
the better we can control the software (in isolation), the more and better testing can be done, automated, and optimized.
what you see is what can be tested. Ability to observe the inputs, outputs, states, internals, error conditions, resource utilization, and other side effects of the system under test.
clarity of specifications.
how often the code changes
how fast it works
how fast defects can be localized.
they simplify integration test by creating mock objects and stubs.
they cannot be directly automatized.
private operations cannot be tested.
private attributes cannot be read.
Define levels (Verbose, Info, Warning, Error, Critical).
Use the last two for urgent notifications.
e.g. CORBA bus.
A testable software allows automated tests.
Good automated tests are:
Repeatable
Easy to write
Easy to understand
Fast
Use Façades to implement the Component Interfaces
Design Operations
Error Handling Design
Refine the Domain Model
Apply Design Patterns to ensure Extensibility
Optimize Attributes and Operations
The Controller is an entry point to the component internal classes
See the Façade Design Pattern and the GRASP Controller.
See the Reactor design pattern
Interaction Diagram
Specify parameters directions (in, out, in/out).
Specify return parameter properties: readOnly, ordered, etc.
context Server::createGame(name: String)
pre:
-- The name of the game should have at least 3 characters
name.size() >= 3
post:
-- There exists an instance of 'Game',
-- which is new and whose name is the same as the parameter 'name'
Game.allInstances() -> exists(each: Game | each.oclIsNew() and each.name = name)Design software that reacts correctly to every situation is complex,
because writing error-free code is complex!
findAvailableInstructors(DateRange): Instructors[*]One or more instances of Instructor is found
No Instructor is found
Network error: timeout, lost package, etc.
Database error: integrity, wrong rights, etc.
What should be returned if no Instructor is found?
A null value ()
An empty list ()
Use Optional<Instructor> ( for single value return)
What should be returned if an error occurs?
An Exception
An integer representing a Status Code
| The value returned cannot be the same as if no value is found! |
Clean code
Hierarchic (like classes)
Richer error information
But:
They can be invisible in the source code.
They create too many possible exit points for an operation.
More lightweight (specially for remote calls)
But:
Error codes can be ignored by the callers.
| Use exceptions for high level and status codes for low level |
In UML exceptions are Signals
They are specified as classes and used as operation parameters and inside Activity Diagrams.
findAvailableInstructors(DateRange,
out DataAccessError): Instructors[*]Split Domain and Technical errors
Exceptions should name the problem, not the thrower
Avoid sending low-level exceptions to higher layers:
Don’t throw the information away:
Use NestedExceptions
try {
// (...)
}
catch (DatabaseException e) {
throw DataAccessError(e);
}
catch (FileException e) {
throw DataAccessError(e);
}…when you need to collect results over several methods, you should add a parameter to the method and pass an object that will collect the results for you…
More Error-related Patterns from the Wiki Wiki Web: |
Look for reuse: make similar what is almost similar:
possible changes of operation signatures and attribute types.
Abstract common behaviors and properties.
Default values
Multiplicities: [0..1], [*], etc.
Constraints (OCL expressions).
Modifiers: readOnly, redefines, etc.
Visibility: +, ~, #, -.
Use OCL to specify derived attribute.
Specify the relationships between different roles: union, subsets <x>, redefines <y>, ordered, unique, etc.
Specify navigability.
Apply the Command design pattern to implement events.



| What varies? | Design Patterns |
|---|---|
Object creation |
|
Created structure |
|
Traversal algorithm |
Apply the Abstract Factory design pattern to dynamically configure products from the same family.

Apply the State design patterns for each state chart.


Remember derived attributes values.


Add (possibly) redundant attributes and associations to speed up calculation.

Load an object only when necessary.
Apply the Proxy pattern.


Separate interface from implementation.
Separate orthogonal concerns: do not try to connect what is independent.
The «what» before the «how».
First specify what a component should do, then $ it does it.
Work on different abstraction levels.
Use rapid prototyping.

When the choice is possible, use aggregation instead of specialization.
Superclasses should be abstract.



If two classes share the same properties but not the same behavior, those properties should be moved to a third class.
The behavior related to these properties will intuitively follow them.
Setter and Getter methods are not mandatory: they prevent good design choices.
Prefer private attributes rather than protected and protected rather than public.
Clients of a class must be dependent on its public interface, but a class should not be dependent on its clients.
copy()
deepCopy()
equals()
fromString()
toString()
etc.
they may be an operation.
in some cases, they are indeed an operation (see Command and Strategy patterns).
Feature envy: operations of a class should use properties (and other operations) of this same class.
Common behavior of public operations should be placed in private methods.
Depth-first design
Requirement direct refinement.
Potential changes misunderstanding.
Design too detailed.
Ambiguous design.
Undocumented design decisions.
Inconsistent design.
XUL, a powerful widget-based markup language.
Includes a set of cross-platform widgets
Based on existing standards
Cascading Style Sheets (CSS)
Document Object Model (DOM)
JavaScript, including E4X (ECMAScript for XML)
XML, SVG, MathML
XUL user interfaces can run on Windows, Mac and Linux (Platform portability).
Easy localization: locale specific resources are easily separated from presentation and code.

<extension point="org.eclipse.ui.actionSets">
<actionSet
id="org.eclipse.actionSet"
label="Sample Action Set"
visible="true">
<menu id="sampleMenu"
label="Sample &Menu">
<separator name="sampleGroup"/>
</menu>
<action
id="org.eclipse.actions.SampleAction">
class="org.eclipse.actions.SampleAction"
menubarPath="sampleMenu/sampleGroup"
toolbarPath="sampleGroup"
label="&Sample Action"
icon="icons/sample.gif"
tooltip="Hello, Eclipse world">
</action>
</actionSet>
</extension>
<extension point="org.eclipse.ui.perspectiveExtensions">
<perspectiveExtension
targetID="org.eclipse.ui.resourcePerspective">
<actionSet id="org.eclipse.actionSet"/>
</perspectiveExtension>
</extension>Avoid miracles: if a functionality is not clear enough, refine the requirements
Do not over-specify technical details, leave choices to the implementation
Some important tips on designing for extensibility: Design for Extensibility by Miguel Castro.