08
  • Software Development

    Complex systems cannot be understood/developed as a whole without abstraction. Using the divide and conquer approach we can breakdown problems into Independent sub-systems which are integrated to solve whole problem. In order to so we need to ask ourselves how to partition system into sub-systems, and how to define communication between sub-systems?

    “The beginning of wisdom for a [software engineer] is to recognize the difference between getting a program to work, and getting it right” (Michael Jackson)

    All abstractions are "incomplete and inaccurate“, their strength lies in the emphasis of important details and rejection of irrelevant ones in current context. Abstractions allow us to postpone decisions concerning implementation for as long as possible rather than prematurely constrain the development space.

    Higher levels of abstraction focus on outlining details of a system and are expressed in a language of problem environment (domain). Lower levels of abstraction will be more detailed focusing in on problem solution and can be easily expressed in a programming language. Software development refines an abstraction into a solution.

    Refinement

    “In each step (of the refinement), one or several instructions of the given program are decomposed into more detailed instructions. The successive decomposition or refinement of specifications terminates when all instructions are expressed in terms of any underlying programming language.

    Every refinement step implies some design decisions. It is important that … the programmer be aware of the underlying criteria (for design decisions) and of the existence of alternative solutions …” (Niklaus Wirth)

  • Functional Decomposition

    Functional Decomposition is the procedure of taking a complex process and breaking it down into smaller, more manageable parts. The process can continue while each part can be broken down further. This allows us to concentrate on one part at a time essentially refining the problem into smaller, simpler problems and then refining again until you reach manageable chunks, moving us from abstraction to a solution detail. In order to carry this process out we must understand how you determine the decomposition.

    Modularity

    Software architecture uses modules, this allows us to partition systems into separately named and addressable components which encourages separation of concerns. We can then Integrate these components into a produce solution. There are two main approaches to modularity, the 'Top Down' approach which is a stepwise refinement based on functional decomposition, and the 'Bottom Up' approach which begins with the design/implementation of sub-problems and combines them to solve a bigger problem.

    Partitioning Diagrams

    Module characteristics

    Modules should be functionally independent, should focus on a single function, and communicate with other modules through well-defined interface. Modules may well be decomposed into sub-modules which allows us to create a module hierarchy. There are several advantages to modules, They:
    Encourages pluggability, easier maintenance, independent development, reuse
    Supports better testing
    Minimises propagation of error/fault
    Helps to manage complexity

    Encapsulation

    "Encapsulation is the process of hiding all of the details of an object that do not contribute to its essential characteristics.“ (Booch)

    "Encapsulation (Information Hiding). A principle, used when developing an overall program structure, that each component of a program should encapsulate or hide a single design decision... The interface to each module is defined in such a way as to reveal as little as possible about its inner workings.“ (Coad)

    Encapsulation allows us to hide the inner workings (code/data) of a module from other modules, allowing design decisions made inside module and hidden from other modules. Well-defined interfaces exposes module’s functionality to the outside world and only communicate what is necessary. This encourages testability, pluggability, independence as well as reducing the “ripple effect” of changes and error propagation.

    On order to properly partition systems we must ask ourselves the following questions:
    How many modules should there be?
    What size should a module be?
    How deep should a module hierarchy go?
    How many interactions should there be between modules?
    What are the best type of interactions?

    Module Size

    Many guidelines have been postulated for module size. In essence a module should carry out one clear identifiable task where there is no identifiable benefit to breaking the task into sub-tasks. A module that looks too complex probably is and remember the simpler the module the easier/cheaper it is to develop. However, smaller modules mean more modules and more communication between modules which increases integration complexity and cost.

    Module Size Diagram

    Control Hierarchy

    Control hierarchy Diagram

    The fan-out of a module is where number of modules are directly controlled by a module, i.e. invoked by it. The fan-in of a module is where number of modules directly control a module, i.e. invoke it. In both cases the all modules subordinate to a module (Scope of Control) and they are all affected by a decision within a module (Scope of Effect). The aim is to keep scope of effect within scope of control, i.e. if a module is effected by the outcome of a decision then the module should be subordinate the module that made the decision.

    Global vs Local Data

    Global-Local Data Diagram

    Module Complexity

    There are many methods for measuring complexity, e.g. 'McCabe’s Cyclomatic Complexity'. McCabe’s cyclomatic complexity counts conditional statements and adds 1, if cyclomatic complexity is greater than ten then is considered too complex. However this method has been criticised as been be arbitrary, i.e. why ten, why not twenty, or five? Furthermore, there are other forms of complexity than conditional statements. Nevertheless, it can be useful in determining test cases:

    Example

    1. read workdatafile; grosswage = hours * rate
    2.  if (grosswage > 50000) then
    3.    tax calculation-1
         else
    4.    tax calculation-2
    5.  netwage = grosswage - other deductions
    6.  if (netwage < 15000) then
    7.    extraallowances
        else
    8.    standardallowance
    9.  print wageslip
    

    📷 Complexity Example 1

    📷 Complexity Example 2

    📷 Complexity Example 3

    📷 Complexity Example 4

    📷 Complexity Example 5

    X
    Module Complexity Diagram 1
    X
    Module Complexity Diagram 2
    X
    Module Complexity Diagram 3
    X
    Module Complexity Diagram 4
    X
    Module Complexity Diagram 5
  • Coupling

    Coupling is the interactions between modules. We want modules to as independent as possible and our goal is to reduce number of interactions. Some forms of interaction are less desirable than others, weak coupling is the goal. There are several levels of coupling:
    Altering another modules code:
    e.g. COBOL ALTER statement could change meaning of code
    Abnormal entry point:
    e.g. older languages used a GOTO statement which allowed you to direct a program to jump into the middle of a module
    Modifying data in another module:
    where possible a module should encapsulate its own data and should be defined as private/protected
    content coupling
    Shared/Global data:
    global/shared data should be avoided as it can be hard to track down which modules are changing data
    common coupling
    Procedure call parameter which is a switch:
    a value passed into a module which determines which path to execute – often a Boolean value
    i.e. a decision in one affects processing in other
    control coupling
    better to call different modules based on the decision
    Procedure call parameters which are pure data:
    nearly ideal, especially if number of parameters is small
    data coupling
    Passing a serial data stream:
    the weakest coupling (best), no transfer of control - data passed as if module writing to a file, receiving module as if reading from a file
    The weakest (best) coupling is therefore achieved by:
    procedure calls with a small number of pure data parameters, or
    passing a serial stream of data from one module to the other
  • Cohesion

    Cohesion defines interactions within a module. All statements within a module should contribute to the single function the method is designed to achieve. Some interactions are less desirable than others, strong cohesion is the goal. There are several levels of cohesion:
    Coincidental:
    statements in module are there by no great design
    difficult to state in words what the module does
    often because of some module size limit
    Logical:
    statements which carry out similar functions e.g. output, grouped together
    not one defined function and overly complex
    Temporal:
    statements which are executed at the same time
    e.g. initialise collections, open files
    functions unrelated
    better to have one initialisation control module which calls other specialised initialisation modules
    Communicational:
    functions acting on common data grouped together
    e.g. calculate and print total price
    carries out a number of different actions on one piece of data
    unnecessarily complex
    Sequential:
    operations collaborate to modify a piece of data
    Functional:
    single well-defined action on a single subject
    e.g. calculate average, obtain date
    strongest form of cohesion – most desirable

    Coupling & Cohesion

    Weak coupling & strong cohesion are good whereas, strong coupling & weak cohesion are bad. Strong cohesion tends to promote weak coupling and vice versa.

    Coupling and Cohesion Diagram
  • Java And Modularity

    Java was not designed to have modules although this is being addressed in Java 9 (Sep 2016). Let’s consider the different Java mechanisms for grouping code (and data) e.g. methods, classes/objects, packages/libraries and JARs. First let’s consider the desirable characteristics of a module facility. Modules are autonomous unit of deployment which are separately compliable/deployable encouraging loose coupling. They also have a consistent and unique identities in the form of module IDs and version. However, as modules evolves a new version is created and older applications may need previous versions. Modules also provide us with documented requirements and dependencies such as standard compile-time, deployment facilities and meta-information. They promote open and understandable interfaces and allow us to hide implementation details (encapsulation).

    Methods

    A method is a named group of statements which has a defined interface through parameters and return statement, methods can declare local data. A well-defined method will have a single function and all statements within the method will contribute to that function. Therefore a method can minimize coupling and be cohesive. Methods are not separately deployable and there is no versioning so they are not really a module.

    Classes

    Again a class (object) can have a well-defined interface which controls interactions with other classes (objects). A class defines its own data and methods should be aimed towards accessing and modifying that data. Therefore, good classes can minimize coupling and be highly cohesive. Where a class has a dependency on another class then access modifiers can be used to limit that connection, though this still increases coupling. Nested classes can be used to aid encapsulation. Classes also have several drawbacks:
    Object identity can be unreliable
    Interfaces are not versioned
    Classes not unique at deployment level
    Loose coupling best practice but not enforced
    Reuse can be difficult when third-party dependencies involved

    However, compile-time tools attempt to address these issues.

    Packages/Libraries

    Packages are the closest mechanism Java has to a module. Packages are a set of classes and interfaces that are grouped together because of some shared purpose. Any Java library is in effect a module often with an API which defines its interface with the outside world. Applications often have one or more dependencies on external libraries, and libraries often have transitive dependencies on others.

    Java Archive (JAR) (Oracle)

    The java archive bundles multiple files into a single archive file. Class files and auxiliary resources are associated with applets and applications. The archive is consistently being updated and improves on many legacy deployment conventions. However the archive does not fulfil all requirements. Frameworks and platforms enhance JAR specification to provide support for modular systems. There are many benefits and some issues with JARs:

    Benefits:
    Security:
    can digitally sign the contents of a JAR file
    users who recognize signature can optionally grant software security privileges it wouldn't otherwise have
    Decreased download time:
    applet is bundled in a JAR file can be downloaded to a browser in a single HTTP transaction without the need for opening a new connection for each file
    Compression:
    JAR format allows you to compress your files for efficient storage
    Packaging for extensions:
    extensions framework provides means to add functionality to the Java core platform
    Package Sealing:
    optionally sealed so package can enforce version consistency
    Package Versioning:
    JAR file can hold data about the files it contains, such as vendor and version information
    Portability:
    mechanism for handling JAR files is a standard part of the Java platform's core API

    Issues:
    No intrinisic unqueness
    rarely used version number in a .jar manifest
    package names of classes & use in classpath only modularity support
    JAR Hell
    problems with class loading
    updated JAR can have same name as existing JAR
    runtime environment cannot distinguish and loads first one it finds
    app requires older version of library not in updated JAR file

    🔗 The Java Modularity Story

    🔗 Modularity in Java 9