Detailed Design

Component Internal Design

LS2N — Nantes Université

Agenda

  • Introduction

  • Guiding Principles

  • Best Practices

  • Case Studies

  • Conclusion

Introduction

Definition

  • The detailed design specifies the internal structure and behavior of components.

The Risk Server Component
Figure 1. The Risk Server Component

Main Goal

design2code

Create a model that can be translated to code effortlessly.



miracle

Making Choices

design choices
  • Designing software means making choices

  • This choices are often driven by software quality factors: 

    • extensibility, performance, portability, testability, etc.

Design for Extensibility

Extensibility

is the ability of a software to allow and accept significant extension of its functionalities without major changes in its design.


Principles
  • An extensible design:

    • is open to changes.

    • addresses a class of problems instead of a single problem.

    • does not contain immature/fuzzy concepts.

Design for Performance

Performance

is the degree to which a system or component accomplishes its designated functions within given constraints regarding processing time and throughput rate.


Efficiency

is the capability of the software product to provide appropriate performance, relative to the amount of resources used under stated conditions.

Design for Performance Principles

  • 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:

Code Optimization Rules [M. Jackson]

  1. First Rule of Program Optimization:

    • Don’t do it

  2. Second Rule of Program Optimization (for experts only!):

    • Don’t do it yet! 

  3. Third Rule of Code Optimization:

    • Profile first.

Design for Portability

  • Portability is the ease with which the software product can be transferred from one hardware or software environment to another. 

  • Levels of portability: 

    Code

    the software is adapted in its source-level, then recompiled for the new target environment. 

    Virtual Machine

    the software is compiled into intermediate form (byte code), and executed on platform-specific virtual machines.  

    Binary

    This the software is ported directly in its executable form, usually with little adaptation. 

Portability Principles

  • 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 at Code Level

  • 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.) 

Portability at Design Level

  • Develop different version of the same component.  

  • For instance, a GUI for: KDE, Gnome, Windows, etc. 

Design for Testability

  • Testability is the capability of a software product to enable modified software to be tested.  

  • Factors that influence testability:

    Controllability

    the better we can control the software (in isolation), the more and better testing can be done, automated, and optimized. 

    Observability

    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.

Other Factors

Suitability

clarity of specifications. 

Stability

how often the code changes 

Performance

how fast it works 

Diagnosability

how fast defects can be localized.  

Controllability

Use a specific port for controlling the component during tests
Figure 2. Use a specific port for controlling the component during tests
Diagram

Improving Controllability

Use interfaces
  • they simplify integration test by creating mock objects and stubs.

Separate Input/Output Interfaces from the rest of the code
  • they cannot be directly automatized.

Observability

Use _Listeners_ to improve Observability
Figure 3. Use Listeners to improve Observability
Diagram

Improving Observability

Use the public visibility when possible:
  • private operations cannot be tested.

  • private attributes cannot be read.

Use a Logging module
  • Define levels (Verbose, Info, Warning, Error, Critical). 

  • Use the last two for urgent notifications.

Use observable middleware
  • e.g. CORBA bus.

Think « Automated Tests »

  • A testable software allows automated tests.

  • Good automated tests are:

    • Repeatable

    • Easy to write

    • Easy to understand

    • Fast

Guiding Principles

Guiding Principles

  1. Use Façades to implement the Component Interfaces

  2. Design Operations

  3. Error Handling Design

  4. Refine the Domain Model

  5. Apply Design Patterns to ensure Extensibility

  6. Optimize Attributes and Operations

GP1: Use Façades to implement the Component Interfaces

The GRASP Controller

Diagram

Controller as a Façade

Diagram
  • The Controller is an entry point to the component internal classes

  • See the Façade Design Pattern and the GRASP Controller.

Synchronous Controller

Diagram

Asynchronous Controller

Diagram

GP2: Design Operations

Operation Design in UML

UML offers different ways to specify an operation
Diagram

Signature specification

  • Specify parameters directions (in, out, in/out).

  • Specify return parameter properties: readOnly, ordered, etc.

Example: Create Game

Server::createGame()
Figure 4. Server::createGame()

Example: Start Game

Sever::startGame()
Figure 5. Sever::startGame()
Client::startGame()
Figure 6. Client::startGame()

Example: Create Game

Example of using OCL pre and post-conditions to describe an operation
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)

Example: Attack

Diagram

GP3: Error Handling Design

Error Handling

A complex and tedious task

Design software that reacts correctly to every situation is complex,
because writing error-free code is complex!

Strategies for Handling Errors

A Simple Operation
findAvailableInstructors(DateRange): Instructors[*]
Possible situations (non-exhaustive):
  1. One or more instances of Instructor is found

  2. No Instructor is found

  3. Network error: timeout, lost package, etc.

  4. Database error: integrity, wrong rights, etc.

Return Value Alternatives

What should be returned if no Instructor is found?

Alternatives:
  • A null value ()

  • An empty list ()

  • Use Optional<Instructor> ( for single value return)

Error Handling Alternatives

What should be returned if an error occurs?

Alternatives:
  • An Exception

  • An integer representing a Status Code



The value returned cannot be the same as if no value is found!

Exceptions and Status Codes

Exceptions:
  • 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.

Status Codes:
  • 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

Exceptions inside UML

Diagram
  • In UML exceptions are Signals

  • They are specified as classes and used as operation parameters and inside Activity Diagrams.


findAvailableInstructors() sends the DataAccessError Signal
findAvailableInstructors(DateRange,
    out DataAccessError): Instructors[*]

Some Tips

  • 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:

try {
  // (...)
}
catch (DatabaseException e) {
  throw DataAccessError(e);
}
catch (FileException e) {
  throw DataAccessError(e);
}

Collecting Parameters Pattern [Beck96]

…​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…​
Diagram

Additional Readings

More Error-related Patterns from the Wiki Wiki Web:

GP4: Refine the Domain Model

Specialization/Generalization Adjustment

  • Look for reuse: make similar what is almost similar:

    • possible changes of operation signatures and attribute types.

  • Abstract common behaviors and properties.

Diagram

Attributes

Specify:
  • Default values

  • Multiplicities: [0..1], [*], etc.

  • Constraints (OCL expressions).

  • Modifiers: readOnly, redefines, etc.

  • Visibility: +, ~, #, -.

  • Use OCL to specify derived attribute.

Diagram

Association Roles

  • Specify the relationships between different roles: union, subsets <x>, redefines <y>, ordered, unique, etc.

  • Specify navigability.

GP5: Apply Design Patterns to ensure Extensibility

Design Patterns for Extensibility (1/4)

What varies?Design Patterns

Algorithm

Actions

Event Reification

  • Apply the Command design pattern to implement events. 

cd events

Applying the Command Pattern

cd events
cd events commands

Design Patterns for Extensibility (2/4)

What varies?Design Patterns

Implementation

  • Bridge

Reactivity to changes

Interaction between objects

Design Patterns for Extensibility (3/4)

What varies?Design Patterns

Object creation

Created structure

  • Builder

Traversal algorithm

Variants Reification

  • Apply the Abstract Factory design pattern to dynamically configure products from the same family.

Applying the Abstract Factory

seminar system abstract factory

Design Patterns for Extensibility (4/4)

What varies?Design Patterns

Object interface

  • Adapter

Object behavior

State Reification

  • Apply the State design patterns for each state chart.

Applying the State Pattern

sm resource
cd resource states

GP6: Optimize Attributes and Operations

Optimization of Attributes

  • Remember derived attributes values. 

cd derived attributes

Optimization of Operations

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

Optimization of Operations

cd event occurrence

Lazy Loading of Objects

  • Load an object only when necessary.

  • Apply the Proxy pattern.

cd classroom

Proxy Pattern Application

cd classroom proxy

Design Best Practices

Best Practices

  • 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.

Distinguish specialization and aggregation

cd specialization aggregation
  • When the choice is possible, use aggregation instead of specialization. 

Avoid Large (God) Classes

large class 01 2x
Figure 7. System composed of a class that controls everything and of several data classes.

Avoid specialization of concrete classes

  • Superclasses should be abstract. 

abstract classes

Super-classes should not know their subclasses

forbiden

Specialization hierarchies should be deep

cd java collection framework

Common Properties

  • 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.

Avoid Accessor/Mutator Methods

Attributes should be hidden:
  • Setter and Getter methods are not mandatory: they prevent good design choices. 

  • Prefer private attributes rather than protected and protected rather than public. 

Class Dependencies

Clients of a class must be dependent on its public interface, but a class should not be dependent on its clients. 

Define a minimal interface for all classes

For instance:
  • copy()

  • deepCopy()

  • equals()

  • fromString()

  • toString()

  • etc.

Classes are not Actions

Be suspicious with classes whose names are verbs:
  • they may be an operation.

  • in some cases, they are indeed an operation (see Command and Strategy patterns).

Operations

  • 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.

Bad Practices

  • Depth-first design

  • Requirement direct refinement.

  • Potential changes misunderstanding.

  • Design too detailed.

  • Ambiguous design.

  • Undocumented design decisions.

  • Inconsistent design.

Case Studies

Portability in Firefox

  • 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. 

Extensibility Example: The Eclipse Project

eclipse project

Eclipse Plugin Configuration

<extension point="org.eclipse.ui.actionSets">
    <actionSet
      id="org.eclipse.actionSet"
      label="Sample Action Set"
      visible="true">
      <menu id="sampleMenu"
            label="Sample &amp;Menu">
        <separator name="sampleGroup"/>
      </menu>
      <action
        id="org.eclipse.actions.SampleAction">
        class="org.eclipse.actions.SampleAction"
        menubarPath="sampleMenu/sampleGroup"
        toolbarPath="sampleGroup"
       label="&amp;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>

Conclusion

Conclusion

  • Avoid miracles: if a functionality is not clear enough, refine the requirements

  • Do not over-specify technical details, leave choices to the implementation

Further Readings