This site is from a past semester! The current version will be here when the new semester starts.
TIC4002 2021 Jan-May
  • Full Timeline
  • Week 1 [Mon, Jan 11th]
  • Week 2 [Fri, Jan 15th]
  • Week 3 [Fri, Jan 22nd]
  • Week 4 [Fri, Jan 29th]
  • Week 5 [Fri, Feb 5th]
  • Week 6 [Fri, Feb 12th]
  • Week 7 [Fri, Feb 19th]
  • Week 8 [Fri, Mar 5th]
  • Week 9 [Fri, Mar 12th]
  • Week 10 [Fri, Mar 19th]
  • Week 11 [Fri, Mar 26th]
  • Week 12 [Fri, Apr 2nd]
  • Week 13 [Fri, Apr 9th]
  • Textbook
  • Admin Info
  • Dashboards
  •  Individual Project (iP):
  • Individual Project Info
  • iP Upstream Repo
  • iP Showcase
  • iP Code Dashboard
  • iP Progress Dashboard

  •  Team Project (tP):
  • Team Project Info
  • tP Upstream Repo (AB3)
  • Team List
  • tP Code Dashboard
  • tP Progress Dashboard
  • Report Bugs
  • Forum
  • Instructors
  • Announcements
  • Files (handouts, submissions etc.)
  • MS Teams link
  • Java Coding Standard
  • Git Conventions
  • Participation Dashboard
  • Week 5 [Fri, Feb 5th] - Topics

    • [W5.1] Code Quality

       Coding Standards

    • [W5.1a] Implementation → Code Quality → Introduction → What :

    • [W5.1b] Implementation → Code Quality → Style → Introduction :

       Code Quality: Naming

    • [W5.1c] Implementation → Code Quality → Naming → Introduction :

    • [W5.1d] Implementation → Code Quality → Naming → Basic → Use nouns for things and verbs for actions :

    • [W5.1e] Implementation → Code Quality → Naming → Basic → Use standard words :

    • [W5.1f] Implementation → Code Quality → Naming → Intermediate → Use name to explain :

    • [W5.1g] Implementation → Code Quality → Naming → Intermediate → Not too long, not too short :

    • [W5.1h] Implementation → Code Quality → Naming → Intermediate → Avoid misleading names :

       Readability

    • [W5.1i] Implementation → Code Quality → Readability → Introduction :

    • [W5.1j] Implementation → Code Quality → Readability → Basic → Avoid long methods :

    • [W5.1k] Implementation → Code Quality → Readability → Basic → Avoid deep nesting :

    • [W5.1l] Implementation → Code Quality → Readability → Basic → Avoid complicated expressions :

    • [W5.1m] Implementation → Code Quality → Readability → Basic → Avoid magic numbers :

    • [W5.1n] Implementation → Code Quality → Readability → Basic → Make the code obvious :

    • [W5.1o] Implementation → Code Quality → Readability → Intermediate → Structure code logically :

    • [W5.1p] Implementation → Code Quality → Readability → Intermediate → Do not 'Trip Up' reader :

    • [W5.1q] Implementation → Code Quality → Readability → Intermediate → Practice KISSing :

    • [W5.1r] Implementation → Code Quality → Readability → Intermediate → Avoid premature optimizations :

    • [W5.1s] Implementation → Code Quality → Readability → Intermediate → SLAP hard :

    • [W5.1t] Implementation → Code Quality → Readability → Advanced → Make the happy path prominent :

       Unsafe Practices

    • [W5.1u] Implementation → Code Quality → Error-Prone Practices → Introduction :

    • [W5.1v] Implementation → Code Quality → Error-Prone Practices → Basic → Use the default branch :

    • [W5.1w] Implementation → Code Quality → Error-Prone Practices → Basic → Don't recycle variables or parameters :

    • [W5.1x] Implementation → Code Quality → Error-Prone Practices → Basic → Avoid empty catch blocks :

    • [W5.1y] Implementation → Code Quality → Error-Prone Practices → Basic → Delete dead code :

    • [W5.1z] Implementation → Code Quality → Error-Prone Practices → Intermediate → Minimize scope of variables :

    • [W5.1A] Implementation → Code Quality → Error-Prone Practices → Intermediate → Minimize code duplication :

       Code Comments

    • [W5.1B] Implementation → Code Quality → Comments → Introduction :

    • [W5.1C] Implementation → Code Quality → Comments → Basic → Do not repeat the obvious :

    • [W5.1D] Implementation → Code Quality → Comments → Basic → Write to the reader :

    • [W5.1E] Implementation → Code Quality → Comments → Intermediate → Explain WHAT and WHY, not HOW :

    • [W5.2] Refactoring
    • [W5.2a] Implementation → Refactoring → What :

    • [W5.2b] Tools → IntelliJ IDEA → Refactoring :

    • [W5.2c] Implementation → Refactoring → How :

    • [W5.2d] Implementation → Refactoring → When :

    • [W5.3] IDEs: Intermediate Features
    • [W5.3a] Implementation → IDEs → Debugging → What :

    • [W5.3b] Tools → IntelliJ IDEA → Debugging: Basic :

    • [W5.3c] Tools → IntelliJ IDEA → Code navigation :

    • [W5.4] Design: High-Level View

       Introduction

    • [W5.4a] Design → Introduction → What :
    • [W5.5] Design: Fundamentals

       Abstraction

    • [W5.5a] Design → Design Fundamentals → Abstraction → What :

       Coupling

    • [W5.5b] Design → Design Fundamentals → Coupling → What :

    • [W5.5c] Design → Design Fundamentals → Coupling → How :

    • [W5.5d] Design → Design Fundamentals → Coupling → Types of coupling :

       Cohesion

    • [W5.5e] Design → Design Fundamentals → Cohesion → What :

    • [W5.5f] Design → Design Fundamentals → Cohesion → How :

    • [W5.6] Architecture
    • [W5.6a] Design → Architecture → Introduction → What

    • [W5.6b] Design → Architecture → Architecture Diagrams → Reading

    • [W5.6c] Design → Architecture → Architecture Diagrams → Drawing

    • [W5.6d] Design → Introduction → Multi-level design

    • [W5.7] Architectural Styles
    • [W5.7a] Design → Architecture → Styles → What

    • [W5.7b] Design → Architecture → Styles → n-Tier Style → What

    • [W5.7c] Design → Architecture → Styles → Client-Server Style → What

    • [W5.7d] Design → Architecture → Styles → Event-Driven Style → What

    • [W5.7e] Design → Architecture → Styles → Transaction Processing Style → What

    • [W5.7f] Design → Architecture → Styles → Service-Oriented Style → What : OPTIONAL

    • [W5.7g] Design → Architecture → Styles → Using styles : OPTIONAL

    • [W5.7h] Design → Architecture → Styles → More styles : OPTIONAL

    • [W5.8] Design Approaches
    • [W5.8a] Design → Design Approaches → Top-down and bottom-up design

    • [W5.8b] Design Approaches → Agile Design → Agile design


    [W5.1] Code Quality


    Coding Standards

    W5.1a :

    Implementation → Code Quality → Introduction → What

    Video

    Can explain the importance of code quality

    Always code as if the person who ends up maintaining your code will be a violent psychopath who knows where you live. -- Martin Golding

    Production code needs to be of high quality. Given how the world is becoming increasingly dependent on software, poor quality code is something no one can afford to tolerate.

    Production Code
    Code being used in an actual product with actual users

    W5.1b :

    Implementation → Code Quality → Style → Introduction

    Video

    Can explain the need for following a standard

    One essential way to improve code quality is to follow a consistent style. That is why software engineers follow a strict coding standard (aka style guide).

    The aim of a coding standard is to make the entire code base look like it was written by one person. A coding standard is usually specific to a programming language and specifies guidelines such as the locations of opening and closing braces, indentation styles and naming styles (e.g. whether to use Hungarian style, Pascal casing, Camel casing, etc.). It is important that the whole team/company uses the same coding standard and that the standard is generally not inconsistent with typical industry practices. If a company's coding standard is very different from what is typically used in the industry, new recruits will take longer to get used to the company's coding style.

    IDEs can help to enforce some parts of a coding standard e.g. indentation rules.

    What is the recommended approach regarding coding standards?

    c

    What is the aim of using a coding standard? How does it help?


    Code Quality: Naming

    W5.1c :

    Implementation → Code Quality → Naming → Introduction

    Can explain the need for good names in code

    Proper naming improves the readability of code. It also reduces bugs caused by ambiguities regarding the intent of a variable or a method.

    There are only two hard things in Computer Science: cache invalidation and naming things. -- Phil Karlton

    W5.1d :

    Implementation → Code Quality → Naming → Basic → Use nouns for things and verbs for actions

    Can improve code quality using technique: use nouns for things and verbs for actions

    Every system is built from a domain-specific language designed by the programmers to describe that system. Functions are the verbs of that language, and classes are the nouns.
    -- Robert C. Martin, Clean Code: A Handbook of Agile Software Craftsmanship

    Use nouns for classes/variables and verbs for methods/functions.

    Examples:

    Name for a Bad Good
    Class CheckLimit LimitChecker
    Method result() calculate()

    Distinguish clearly between single-valued and multi-valued variables.

    Examples:

    Good

    Person student;
    ArrayList<Person> students;
    

    Good

    name = 'Jim'
    names = ['Jim', 'Alice']
    

    W5.1e :

    Implementation → Code Quality → Naming → Basic → Use standard words

    Can improve code quality using technique: use standard words

    Use correct spelling in names. Avoid 'texting-style' spelling. Avoid foreign language words, slang, and names that are only meaningful within specific contexts/times e.g. terms from private jokes, a TV show currently popular in your country.

    W5.1f :

    Implementation → Code Quality → Naming → Intermediate → Use name to explain

    Can improve code quality using technique: use name to explain

    A name is not just for differentiation; it should explain the named entity to the reader accurately and at a sufficient level of detail.

    Examples:

    Bad Good
    processInput() (what 'process'?) removeWhiteSpaceFromInput()
    flag isValidInput
    temp

    If a name has multiple words, they should be in a sensible order.

    Examples:

    Bad Good
    bySizeOrder() orderBySize()

    Imagine going to the doctor's and saying "My eye1 is swollen"! Don’t use numbers or case to distinguish names.

    Examples:

    Bad Bad Good
    value1, value2 value, Value originalValue, finalValue

    W5.1g :

    Implementation → Code Quality → Naming → Intermediate → Not too long, not too short

    Can improve code quality using technique: not too long, not too short

    While it is preferable not to have lengthy names, names that are 'too short' are even worse. If you must abbreviate or use acronyms, do it consistently. Explain their full meaning at an obvious location.

    W5.1h :

    Implementation → Code Quality → Naming → Intermediate → Avoid misleading names

    Can improve code quality using technique: avoid misleading names

    Related things should be named similarly, while unrelated things should NOT.

    Example: Consider these variables

    • colorBlack: hex value for color black
    • colorWhite: hex value for color white
    • colorBlue: number of times blue is used
    • hexForRed: hex value for color red

    This is misleading because colorBlue is named similar to colorWhite and colorBlack but has a different purpose while hexForRed is named differently but has a very similar purpose to the first two variables. The following is better:

    • hexForBlack hexForWhite hexForRed
    • blueColorCount

    Avoid misleading or ambiguous names (e.g. those with multiple meanings), similar sounding names, hard-to-pronounce ones (e.g. avoid ambiguities like "is that a lowercase L, capital I or number 1?", or "is that number 0 or letter O?"), almost similar names.

    Examples:

    Bad Good Reason
    phase0 phaseZero Is that zero or letter O?
    rwrLgtDirn rowerLegitDirection Hard to pronounce
    right left wrong rightDirection leftDirection wrongResponse right is for 'correct' or 'opposite of 'left'?
    redBooks readBooks redColorBooks booksRead red and read (past tense) sounds the same
    FiletMignon egg If the requirement is just a name of a food, egg is a much easier to type/say choice than FiletMignon


    Readability

    Video

    W5.1i :

    Implementation → Code Quality → Readability → Introduction

    Can explain the importance of readability

    Programs should be written and polished until they acquire publication quality. --Niklaus Wirth

    Among various dimensions of code quality, such as run-time efficiency, security, and robustness, one of the most important is understandability. This is because in any non-trivial software project, code needs to be read, understood, and modified by other developers later on. Even if you do not intend to pass the code to someone else, code quality is still important because you will become a 'stranger' to your own code someday.

    The two code samples given below achieve the same functionality, but one is easier to read.

    Bad

    int subsidy() {
        int subsidy;
        if (!age) {
            if (!sub) {
                if (!notFullTime) {
                    subsidy = 500;
                } else {
                    subsidy = 250;
                }
            } else {
                subsidy = 250;
            }
        } else {
            subsidy = -1;
        }
        return subsidy;
    }
    
      

    Good

    int calculateSubsidy() {
        int subsidy;
        if (isSenior) {
            subsidy = REJECT_SENIOR;
        } else if (isAlreadySubsidized) {
            subsidy = SUBSIDIZED_SUBSIDY;
        } else if (isPartTime) {
            subsidy = FULLTIME_SUBSIDY * RATIO;
        } else {
            subsidy = FULLTIME_SUBSIDY;
        }
        return subsidy;
    }
    

    Bad

    def calculate_subs():
        if not age:
            if not sub:
                if not not_fulltime:
                    subsidy = 500
                else:
                    subsidy = 250
            else:
                subsidy = 250
        else:
            subsidy = -1
        return subsidy
    
      

    Good

    def calculate_subsidy():
        if is_senior:
            return REJECT_SENIOR
        elif is_already_subsidized:
            return SUBSIDIZED_SUBSIDY
        elif is_parttime:
            return FULLTIME_SUBSIDY * RATIO
        else:
            return FULLTIME_SUBSIDY
    

    W5.1j :

    Implementation → Code Quality → Readability → Basic → Avoid long methods

    Can improve code quality using technique: avoid long methods

    Be wary when a method is longer than the computer screen, and take corrective action when it goes beyond 30 LOC (lines of code). The bigger the haystack, the harder it is to find a needle.

    W5.1k :

    Implementation → Code Quality → Readability → Basic → Avoid deep nesting

    Can improve code quality using technique: avoid deep nesting

    If you need more than 3 levels of indentation, you're screwed anyway, and should fix your program. --Linux 1.3.53 Coding Style

    In particular, avoid arrowhead style code.

    A real code example:

    Bad

    int subsidy() {
        int subsidy;
        if (!age) {
            if (!sub) {
                if (!notFullTime) {
                    subsidy = 500;
                } else {
                    subsidy = 250;
                }
            } else {
                subsidy = 250;
            }
        } else {
            subsidy = -1;
        }
        return subsidy;
    }
    
      

    Good

    int calculateSubsidy() {
        int subsidy;
        if (isSenior) {
            subsidy = REJECT_SENIOR;
        } else if (isAlreadySubsidized) {
            subsidy = SUBSIDIZED_SUBSIDY;
        } else if (isPartTime) {
            subsidy = FULLTIME_SUBSIDY * RATIO;
        } else {
            subsidy = FULLTIME_SUBSIDY;
        }
        return subsidy;
    }
    

    Bad

    def calculate_subs():
        if not age:
            if not sub:
                if not not_fulltime:
                    subsidy = 500
                else:
                    subsidy = 250
            else:
                subsidy = 250
        else:
            subsidy = -1
        return subsidy
    
      

    Good

    def calculate_subsidy():
        if is_senior:
            return REJECT_SENIOR
        elif is_already_subsidized:
            return SUBSIDIZED_SUBSIDY
        elif is_parttime:
            return FULLTIME_SUBSIDY * RATIO
        else:
            return FULLTIME_SUBSIDY
    

    W5.1l :

    Implementation → Code Quality → Readability → Basic → Avoid complicated expressions

    Can improve code quality using technique: avoid complicated expressions

    Avoid complicated expressions, especially those having many negations and nested parentheses. If you must evaluate complicated expressions, have it done in steps (i.e. calculate some intermediate values first and use them to calculate the final value).

    Example:

    Bad

    return ((length < MAX_LENGTH) || (previousSize != length)) && (typeCode == URGENT);
    

    Good

    boolean isWithinSizeLimit = length < MAX_LENGTH;
    boolean isSameSize = previousSize != length;
    boolean isValidCode = isWithinSizeLimit || isSameSize;
    
    boolean isUrgent = typeCode == URGENT;
    
    return isValidCode && isUrgent;
    

    Example:

    Bad

    return ((length < MAX_LENGTH) or (previous_size != length)) and (type_code == URGENT)
    

    Good

    is_within_size_limit = length < MAX_LENGTH
    is_same_size = previous_size != length
    is_valid_code = is_within_size_limit or is_same_size
    
    is_urgent = type_code == URGENT
    
    return is_valid_code and is_urgent
    

    The competent programmer is fully aware of the strictly limited size of his own skull; therefore he approaches the programming task in full humility, and among other things he avoids clever tricks like the plague. -- Edsger Dijkstra

    W5.1m :

    Implementation → Code Quality → Readability → Basic → Avoid magic numbers

    Can improve code quality using technique: avoid magic numbers

    When the code has a number that does not explain the meaning of the number, it is called a "magic number" (as in "the number appears as if by magic"). Using a e.g., PInamed constant makes the code easier to understand because the name tells us more about the meaning of the number.

    Example:

    Bad

    return 3.14236;
    ...
    return 9;
    
      

    Good

    static final double PI = 3.14236;
    static final int MAX_SIZE = 10;
    ...
    return PI;
    ...
    return MAX_SIZE - 1;
    

    Note: Python does not have a way to make a variable a constant. However, you can use a normal variable with an ALL_CAPS name to simulate a constant.

    Bad

    return 3.14236
    ...
    return 9
    
      

    Good

    PI = 3.14236
    MAX_SIZE = 10
    ...
    return PI
    ...
    return MAX_SIZE - 1
    

    Similarly, you can have ‘magic’ values of other data types.

    Bad

    return "Error 1432"; // A magic string!
    
    return "Error 1432" # A magic string!
    

    In general, try to avoid any magic literals.

    W5.1n :

    Implementation → Code Quality → Readability → Basic → Make the code obvious

    Can improve code quality using technique: make the code obvious

    Make the code as explicit as possible, even if the language syntax allows them to be implicit. Here are some examples:

    • [Java] Use explicit type conversion instead of implicit type conversion.
    • [Java, Python] Use parentheses/braces to show groupings even when they can be skipped.
    • [Java, Python] Use enumerations when a certain variable can take only a small number of finite values. For example, instead of declaring the variable 'state' as an integer and using values 0, 1, 2 to denote the states 'starting', 'enabled', and 'disabled' respectively, declare 'state' as type SystemState and define an enumeration SystemState that has values 'STARTING', 'ENABLED', and 'DISABLED'.

    W5.1o :

    Implementation → Code Quality → Readability → Intermediate → Structure code logically

    Can improve code quality using technique: structure code logically

    Lay out the code so that it adheres to the logical structure. The code should read like a story. Just like how you use section breaks, chapters and paragraphs to organize a story, use classes, methods, indentation and line spacing in your code to group related segments of the code. For example, you can use blank lines to group related statements together.

    Sometimes, the correctness of your code does not depend on the order in which you perform certain intermediary steps. Nevertheless, this order may affect the clarity of the story you are trying to tell. Choose the order that makes the story most readable.

    W5.1p :

    Implementation → Code Quality → Readability → Intermediate → Do not 'Trip Up' reader

    Can improve code quality using technique: do not 'trip up' reader

    Avoid things that would make the reader go ‘huh?’, such as,

    • unused parameters in the method signature
    • similar things that look different
    • different things that look similar
    • multiple statements in the same line
    • data flow anomalies such as, pre-assigning values to variables and modifying it without any use of the pre-assigned value

    W5.1q :

    Implementation → Code Quality → Readability → Intermediate → Practice KISSing

    Can improve code quality using technique: practice KISSing

    As the old adage goes, "keep it simple, stupid” (KISS). Do not try to write ‘clever’ code. For example, do not dismiss the brute-force yet simple solution in favor of a complicated one because of some ‘supposed benefits’ such as 'better reusability' unless you have a strong justification.

    Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it. -- Brian W. Kernighan

    Programs must be written for people to read, and only incidentally for machines to execute. -- Abelson and Sussman

    W5.1r :

    Implementation → Code Quality → Readability → Intermediate → Avoid premature optimizations

    Can improve code quality using technique: avoid premature optimizations

    Optimizing code prematurely has several drawbacks:

    • You may not know which parts are the real performance bottlenecks. This is especially the case when the code undergoes transformations (e.g. compiling, minifying, transpiling, etc.) before it becomes an executable. Ideally, you should use a profiler tool to identify the actual bottlenecks of the code first, and optimize only those parts.
    • Optimizing can complicate the code, affecting correctness and understandability.
    • Hand-optimized code can be harder for the compiler to optimize (the simpler the code, the easier it is for the compiler to optimize). In many cases, a compiler can do a better job of optimizing the runtime code if you don't get in the way by trying to hand-optimize the source code.

    A popular saying in the industry is make it work, make it right, make it fast which means in most cases, getting the code to perform correctly should take priority over optimizing it. If the code doesn't work correctly, it has no value no matter how fast/efficient it is.

    Premature optimization is the root of all evil in programming. -- Donald Knuth

    Note that there are cases where optimizing takes priority over other things e.g. when writing code for resource-constrained environments. This guideline is simply a caution that you should optimize only when it is really needed.

    W5.1s :

    Implementation → Code Quality → Readability → Intermediate → SLAP hard

    Can improve code quality using technique: SLAP hard

    Avoid varying the level of abstraction within a code fragment. Note: The book The Productive Programmer (by Neal Ford) calls this the Single Level of Abstraction Principle (SLAP) while the book Clean Code (by Robert C. Martin) calls this One Level of Abstraction per Function.

    Example:

    Bad (readData(); and salary = basic * rise + 1000; are at different levels of abstraction)

    readData();
    salary = basic * rise + 1000;
    tax = (taxable ? salary * 0.07 : 0);
    displayResult();
    

    Good (all statements are at the same level of abstraction)

    readData();
    processData();
    displayResult();
    

    Design → Design Fundamentals → Abstraction →

    What

    Abstraction is a technique for dealing with complexity. It works by establishing a level of complexity we are interested in, and suppressing the more complex details below that level.

    The guiding principle of abstraction is that only details that are relevant to the current perspective or the task at hand need to be considered. As most programs are written to solve complex problems involving large amounts of intricate details, it is impossible to deal with all these details at the same time. That is where abstraction can help.

    Data abstraction: abstracting away the lower level data items and thinking in terms of bigger entities

    Within a certain software component, you might deal with a user data type, while ignoring the details contained in the user data item such as name, and date of birth. These details have been ‘abstracted away’ as they do not affect the task of that software component.

    Control abstraction: abstracting away details of the actual control flow to focus on tasks at a higher level

    print(“Hello”) is an abstraction of the actual output mechanism within the computer.

    Abstraction can be applied repeatedly to obtain progressively higher levels of abstraction.

    An example of different levels of data abstraction: a File is a data item that is at a higher level than an array and an array is at a higher level than a bit.

    An example of different levels of control abstraction: execute(Game) is at a higher level than print(Char) which is at a higher level than an Assembly language instruction MOV.

    Abstraction is a general concept that is not limited to just data or control abstractions.

    Some more general examples of abstraction:

    • An OOP class is an abstraction over related data and behaviors.
    • An architecture is a higher-level abstraction of the design of a software.
    • Models (e.g., UML models) are abstractions of some aspect of reality.

    W5.1t :

    Implementation → Code Quality → Readability → Advanced → Make the happy path prominent

    Can improve code quality using technique: make the happy path prominent

    The happy path (i.e. the execution path taken when everything goes well) should be clear and prominent in your code. Restructure the code to make the happy path unindented as much as possible. It is the ‘unusual’ cases that should be indented. Someone reading the code should not get distracted by alternative paths taken when error conditions happen. One technique that could help in this regard is the use of guard clauses.

    Example:

    Bad

    if (!isUnusualCase) {  //detecting an unusual condition
        if (!isErrorCase) {
            start();    //main path
            process();
            cleanup();
            exit();
        } else {
            handleError();
        }
    } else {
        handleUnusualCase(); //handling that unusual condition
    }
    

    In the code above,

    • unusual condition detections are separated from their handling.
    • the main path is nested deeply.

    Good

    if (isUnusualCase) { //Guard Clause
        handleUnusualCase();
        return;
    }
    
    if (isErrorCase) { //Guard Clause
        handleError();
        return;
    }
    
    start();
    process();
    cleanup();
    exit();
    

    In contrast, the above code

    • deals with unusual conditions as soon as they are detected so that the reader doesn't have to remember them for long.
    • keeps the main path un-indented.


    Unsafe Practices

    W5.1u :

    Implementation → Code Quality → Error-Prone Practices → Introduction

    Can explain the need for avoiding error-prone shortcuts

    It is safer to use language constructs in the way they are meant to be used, even if the language allows shortcuts. Such coding practices are common sources of bugs. Know them and avoid them.

    W5.1v :

    Implementation → Code Quality → Error-Prone Practices → Basic → Use the default branch

    Can improve code quality using technique: use the default branch

    Always include a default branch in case statements.

    Furthermore, use it for the intended default action and not just to execute the last option. If there is no default action, you can use the default branch to detect errors (i.e. if execution reached the default branch, raise a suitable error). This also applies to the final else of an if-else construct. That is, the final else should mean 'everything else', and not the final option. Do not use else when an if condition can be explicitly specified, unless there is absolutely no other possibility.

    Bad

    if (red) print "red";
    else print "blue";
    

    Good

    if (red) print "red";
    else if (blue) print "blue";
    else error("incorrect input");
    

    W5.1w :

    Implementation → Code Quality → Error-Prone Practices → Basic → Don't recycle variables or parameters

    Can improve code quality using technique: don't recycle variables or parameters

    • Use one variable for one purpose. Do not reuse a variable for a different purpose other than its intended one, just because the data type is the same.
    • Do not reuse formal parameters as local variables inside the method.

    Bad

    double computeRectangleArea(double length, double width) {
        length = length * width;
        return length;
    }
    
    def compute_rectangle_area(length, width):
        length = length * width
        return length
    

    Good

    double computeRectangleArea(double length, double width) {
        double area;
        area = length * width;
        return area;
    }
    
    def compute_rectangle_area(length, width):
        area = length * width
        return area
    }
    

    W5.1x :

    Implementation → Code Quality → Error-Prone Practices → Basic → Avoid empty catch blocks

    Can improve code quality using technique: avoid empty catch blocks

    Never write an empty catch statement. At least give a comment to explain why the catch block is left empty.

    W5.1y :

    Implementation → Code Quality → Error-Prone Practices → Basic → Delete dead code

    Can improve code quality using technique: delete dead code

    You might feel reluctant to delete code you have painstakingly written, even if you have no use for that code anymore ("I spent a lot of time writing that code; what if I need it again?"). Consider all code as baggage you have to carry; get rid of unused code the moment it becomes redundant. If you need that code again, simply recover it from the revision control tool you are using. Deleting code you wrote previously is a sign that you are improving.

    W5.1z :

    Implementation → Code Quality → Error-Prone Practices → Intermediate → Minimize scope of variables

    Can improve code quality using technique: minimize scope of variables

    Minimize global variables. Global variables may be the most convenient way to pass information around, but they do create implicit links between code segments that use the global variable. Avoid them as much as possible.

    Define variables in the least possible scope. For example, if the variable is used only within the if block of the conditional statement, it should be declared inside that if block.

    The most powerful technique for minimizing the scope of a local variable is to declare it where it is first used. -- Effective Java, by Joshua Bloch

    Resources:

    W5.1A :

    Implementation → Code Quality → Error-Prone Practices → Intermediate → Minimize code duplication

    Can improve code quality using technique: minimize code duplication

    Code duplication, especially when you copy-paste-modify code, often indicates a poor quality implementation. While it may not be possible to have zero duplication, always think twice before duplicating code; most often there is a better alternative.

    This guideline is closely related to the DRY Principle.

    Principles →

    DRY principle

    DRY (Don't Repeat Yourself) principle: Every piece of knowledge must have a single, unambiguous, authoritative representation within a system. -- The Pragmatic Programmer, by Andy Hunt and Dave Thomas

    This principle guards against the duplication of information.

    A functionality being implemented twice is a violation of the DRY principle even if the two implementations are different.

    The value of a system-wide timeout being defined in multiple places is a violation of DRY.


    Code Comments

    Video

    W5.1B :

    Implementation → Code Quality → Comments → Introduction

    Can explain the need for commenting minimally but sufficiently

    Good code is its own best documentation. As you’re about to add a comment, ask yourself, ‘How can I improve the code so that this comment isn’t needed?’ Improve the code and then document it to make it even clearer. -- Steve McConnell, Author of Clean Code

    Some think commenting heavily increases the 'code quality'. That is not so. Avoid writing comments to explain bad code. Improve the code to make it self-explanatory.

    W5.1C :

    Implementation → Code Quality → Comments → Basic → Do not repeat the obvious

    Can improve code quality using technique: do not repeat the obvious

    If the code is self-explanatory, refrain from repeating the description in a comment just for the sake of 'good documentation'.

    Bad

    //increment x
    x++;
    
    //trim the input
    trimInput();
    

    Bad

    # increment x
    x = x + 1
    
    # trim the input
    trim_input()
    

    W5.1D :

    Implementation → Code Quality → Comments → Basic → Write to the reader

    Can improve code quality using technique: write to the reader

    Do not write comments as if they are private notes to yourself. Instead, write them well enough to be understood by another programmer. One type of comment that is almost always useful is the header comment that you write for a class or an operation to explain its purpose.

    Examples:

    Bad Reason: this comment will only make sense to the person who wrote it

    // a quick trim function used to fix bug I detected overnight
    void trimInput() {
        ....
    }
    

    Good

    /** Trims the input of leading and trailing spaces */
    void trimInput() {
        ....
    }
    

    Bad Reason: this comment will only make sense to the person who wrote it

    def trim_input():
    """a quick trim function used to fix bug I detected overnight"""
        ...
    

    Good

    def trim_input():
    """Trim the input of leading and trailing spaces"""
        ...
    

    W5.1E :

    Implementation → Code Quality → Comments → Intermediate → Explain WHAT and WHY, not HOW

    Can improve code quality using technique: explain what and why, not how

    Comments should explain the what and why aspects of the code, rather than the how aspect.

    What: The specification of what the code is supposed to do. The reader can compare such comments to the implementation to verify if the implementation is correct.

    Example: This method is possibly buggy because the implementation does not seem to match the comment. In this case, the comment could help the reader to detect the bug.

    /** Removes all spaces from the {@code input} */
    void compact(String input) {
        input.trim();
    }
    

    Why: The rationale for the current implementation.

    Example: Without this comment, the reader will not know the reason for calling this method.

    // Remove spaces to comply with IE23.5 formatting rules
    compact(input);
    

    How: The explanation for how the code works. This should already be apparent from the code, if the code is self-explanatory. Adding comments to explain the same thing is redundant.

    Example:

    Bad Reason: Comment explains how the code works.

    // return true if both left end and right end are correct or the size has not incremented
    return (left && right) || (input.size() == size);
    

    Good Reason: Code refactored to be self-explanatory. Comment no longer needed.

    boolean isSameSize = (input.size() == size);
    return (isLeftEndCorrect && isRightEndCorrect) || isSameSize;
    

    [W5.2] Refactoring

    Video

    W5.2a :

    Implementation → Refactoring → What

    Can explain refactoring

    The first version of the code you write may not be of production quality. It is OK to first concentrate on making the code work, rather than worry over the quality of the code, as long as you improve the quality later. This process of improving a program's internal structure in small steps without modifying its external behavior is called refactoring.

    • Refactoring is not rewriting: Discarding poorly-written code entirely and re-writing it from scratch is not refactoring because refactoring needs to be done in small steps.
    • Refactoring is not bug fixing: By definition, refactoring is different from bug fixing or any other modifications that alter the external behavior (e.g. adding a feature) of the component in concern.

    Improving code structure can have many secondary benefits: e.g.

    • hidden bugs become easier to spot
    • improve performance (sometimes, simpler code runs faster than complex code because simpler code is easier for the compiler to optimize).

    Given below are two common refactorings (more).

    Refactoring Name: Consolidate Duplicate Conditional Fragments

    Situation: The same fragment of code is in all branches of a conditional expression.

    Method: Move it outside of the expression.

    Example:

    if (isSpecialDeal()) {
        total = price * 0.95;
        send();
    } else {
        total = price * 0.98;
        send();
    }
    
     → 
    if (isSpecialDeal()) {
        total = price * 0.95;
    } else {
        total = price * 0.98;
    }
    send();
    
    
    if is_special_deal:
        total = price * 0.95
        send()
    else:
        total = price * 0.98
        send()
    
     → 
    if is_special_deal:
        total = price * 0.95
    else:
        total = price * 0.98
        
    send()
    

    Refactoring Name: Extract Method

    Situation: You have a code fragment that can be grouped together.

    Method: Turn the fragment into a method whose name explains the purpose of the method.

    Example:

    void printOwing() {
        printBanner();
    
        // print details
        System.out.println("name:	" + name);
        System.out.println("amount	" + getOutstanding());
    }
    

    void printOwing() {
        printBanner();
        printDetails(getOutstanding());
    }
    
    void printDetails(double outstanding) {
        System.out.println("name:	" + name);
        System.out.println("amount	" + outstanding);
    }
    
    def print_owing():
        print_banner()
    
        # print details
        print("name:	" + name)
        print("amount	" + get_outstanding())
    

    def print_owing():
        print_banner()
        print_details(get_outstanding())
    
    def print_details(amount):
        print("name:	" + name)
        print("amount	" + amount)
    

    Some IDEs have builtin support for basic refactorings such as automatically renaming a variable/method/class in all places it has been used.

    Refactoring, even if done with the aid of an IDE, may still result in regressions. Therefore, each small refactoring should be followed by regression testing.

    Choose the correct statements.

    • a. Refactoring can improve understandability
    • b. Refactoring can uncover bugs
    • c. Refactoring can result in better performance
    • d. Refactoring can change the number of methods/classes

    a, b, c, d

    Explanation:

    • (a, b, c) Although the primary aim of refactoring is to improve the internal code structure, there are other secondary benefits.
    • (d) Some refactorings result in adding/removing methods/classes.

    Do you agree with the following statement? Justify your answer.

    Statement: Whenever you refactor code to fix bugs, you need not do regression testing if the bug fix was minor.

    There are two flaws in the given statement.

    DISAGREE.

    1. Even a minor change can have major repercussions on the system. You MUST do regression testing after each change, no matter how minor it is.
    2. Fixing bugs is technically not refactoring.

    Explain what refactoring is and why it is not the same as rewriting, bug fixing, or adding features.

    W5.2b :

    Tools → IntelliJ IDEA → Refactoring

    Can use automated refactoring features of the IDE

    This video explains how to automate the 'Extract variable' refactoring using IntelliJ IDEA. Most other refactorings available work similarly. i.e. select the code to refactorfind the refactoring in the context menu or use the keyboard shortcut.

    Here's another video explaining how to do some more useful refactorings in Intellij IDEA.

    W5.2c :

    Implementation → Refactoring → How

    Can apply some basic refactoring

    Given below are some more commonly used refactorings. A more comprehensive list is available at refactoring-catalog.

    1. Consolidate Conditional Expression
    2. Decompose Conditional
    3. Inline Method
    4. Remove Double Negative
    5. Replace Magic Literal
    6. Replace Nested Conditional with Guard Clauses
    7. Replace Parameter with Explicit Methods
    8. Reverse Conditional
    9. Split Loop
    10. Split Temporary Variable

    W5.2d :

    Implementation → Refactoring → When

    Can decide when to apply a given refactoring

    You know that it is important to refactor frequently so as to avoid the accumulation of ‘messy’ code which might get out of control. But how much refactoring is too much refactoring? It is too much refactoring when the benefits no longer justify the cost. The costs and the benefits depend on the context. That is why some refactorings are ‘opposites’ of each other (e.g. extract method vs inline method).

    ‘Extract method’ and ‘Inline method’ refactorings

    a

    [W5.3] IDEs: Intermediate Features

    W5.3a :

    Implementation → IDEs → Debugging → What

    Video

    Can explain debugging

    Debugging is the process of discovering defects in the program. Here are some approaches to debugging:

    • Bad -- By inserting temporary print statements: This is an ad-hoc approach in which print statements are inserted in the program to print information relevant to debugging, such as variable values. e.g. Exiting process() method, x is 5.347. This approach is not recommended due to these reasons:
      • Incurs extra effort when inserting and removing the print statements.
      • These extraneous program modifications increase the risk of introducing errors into the program.
      • These print statements, if not removed promptly after the debugging, may even appear unexpectedly in the production version.
    • Bad -- By manually tracing through the code: Otherwise known as ‘eye-balling’, this approach doesn't have the cons of the previous approach, but it too is not recommended (other than as a 'quick try') due to these reasons:
      • It is a difficult, time consuming, and error-prone technique.
      • If you didn't spot the error while writing the code, you might not spot the error when reading the code either.
    • Good -- Using a debugger: A debugger tool allows you to pause the execution, then step through the code one statement at a time while examining the internal state if necessary. Most IDEs come with an inbuilt debugger. This is the recommended approach for debugging.

    W5.3b :

    Tools → IntelliJ IDEA → Debugging: Basic

    Can step through a program using a debugger

    This video (from LaunchCode) gives a pretty good explanation of how to use the IntelliJ IDEA debugger.

    W5.3c :

    Tools → IntelliJ IDEA → Code navigation

    Can navigate code effectively using IDE features

    Some useful navigation shortcuts:

    1. Quickly locate a file by name.
    2. Go to the definition of a method from where it is used.
    3. Go back to the previous location.
    4. View the documentation of a method from where the method is being used, without navigating to the method itself.
    5. Find where a method/field is being used.

    [W5.4] Design: High-Level View


    Introduction

    W5.4a :

    Design → Introduction → What

    Can explain what is software design

    Design is the creative process of transforming the problem into a solution; the solution is also called design. -- 📖 Software Engineering Theory and Practice, Shari Lawrence; Atlee, Joanne M. Pfleeger

    Software design has two main aspects:

    • Product/external design: designing the external behavior of the product to meet the users' requirements. This is usually done by product designers with input from business analysts, user experience experts, user representatives, etc.
    • Implementation/internal design: designing how the product will be implemented to meet the required external behavior. This is usually done by software architects and software engineers.

    [W5.5] Design: Fundamentals

    Video


    Abstraction

    W5.5a :

    Design → Design Fundamentals → Abstraction → What

    Can explain abstraction

    Abstraction is a technique for dealing with complexity. It works by establishing a level of complexity we are interested in, and suppressing the more complex details below that level.

    The guiding principle of abstraction is that only details that are relevant to the current perspective or the task at hand need to be considered. As most programs are written to solve complex problems involving large amounts of intricate details, it is impossible to deal with all these details at the same time. That is where abstraction can help.

    Data abstraction: abstracting away the lower level data items and thinking in terms of bigger entities

    Within a certain software component, you might deal with a user data type, while ignoring the details contained in the user data item such as name, and date of birth. These details have been ‘abstracted away’ as they do not affect the task of that software component.

    Control abstraction: abstracting away details of the actual control flow to focus on tasks at a higher level

    print(“Hello”) is an abstraction of the actual output mechanism within the computer.

    Abstraction can be applied repeatedly to obtain progressively higher levels of abstraction.

    An example of different levels of data abstraction: a File is a data item that is at a higher level than an array and an array is at a higher level than a bit.

    An example of different levels of control abstraction: execute(Game) is at a higher level than print(Char) which is at a higher level than an Assembly language instruction MOV.

    Abstraction is a general concept that is not limited to just data or control abstractions.

    Some more general examples of abstraction:

    • An OOP class is an abstraction over related data and behaviors.
    • An architecture is a higher-level abstraction of the design of a software.
    • Models (e.g., UML models) are abstractions of some aspect of reality.


    Coupling

    W5.5b :

    Design → Design Fundamentals → Coupling → What

    Can explain coupling

    Coupling is a measure of the degree of dependence between components, classes, methods, etc. Low coupling indicates that a component is less dependent on other components. High coupling (aka tight coupling or strong coupling) is discouraged due to the following disadvantages:

    • Maintenance is harder because a change in one module could cause changes in other modules coupled to it (i.e. a ripple effect).
    • Integration is harder because multiple components coupled with each other have to be integrated at the same time.
    • Testing and reuse of the module is harder due to its dependence on other modules.

    In the example below, design A appears to have more coupling between the components than design B.

    Discuss the coupling levels of alternative designs x and y.

    Overall coupling levels in x and y seem to be similar (neither has more dependencies than the other). (Note that the number of dependency links is not a definitive measure of the level of coupling. Some links may be stronger than the others.). However, in x, A is highly-coupled to the rest of the system while B, C, D, and E are standalone (do not depend on anything else). In y, no component is as highly-coupled as A of x. However, only D and E are standalone.

    Explain the link (if any) between regressions and coupling.

    When the system is highly-coupled, the risk of regressions is higher too e.g. when component A is modified, all components ‘coupled’ to component A risk ‘unintended behavioral changes’.

    Discuss the relationship between coupling and a measure of how easily a given component can be testedtestability.

    Coupling decreases testability because if the Software Under TestSUT is coupled to many other components, it becomes difficult to test the SUT in isolation of its dependencies.

    Choose the correct statements.

    • a. As coupling increases, testability decreases.
    • b. As coupling increases, the risk of regression increases.
    • c. As coupling increases, the value of automated regression testing increases.
    • d. As coupling increases, integration becomes easier as everything is connected together.
    • e. As coupling increases, maintainability decreases.

    (a)(b)(c)(d)(e)

    Explanation: High coupling means either more components are required to be integrated at once in a big-bang fashion (increasing the risk of things going wrong) or more drivers and stubs are required when integrating incrementally.

    W5.5c :

    Design → Design Fundamentals → Coupling → How

    Can reduce coupling

    X is coupled to Y if a change to Y can potentially require a change in X.

    If the Foo class calls the method Bar#read(), Foo is coupled to Bar because a change to Bar can potentially (but not always) require a change in the Foo class e.g. if the signature of Bar#read() is changed, Foo needs to change as well, but a change to the Bar#write() method may not require a change in the Foo class because Foo does not call Bar#write().

    class Foo {
        ...
        new Bar().read();
        ...
    }
    
    class Bar {
        void read() {
            ...
        }
        
        void write() {
            ...
        }
    }
    

    Some examples of coupling: A is coupled to B if,

    • A has access to the internal structure of B (this results in a very high level of coupling)
    • A and B depend on the same global variable
    • A calls B
    • A receives an object of B as a parameter or a return value
    • A inherits from B
    • A and B are required to follow the same data format or communication protocol

    Which of these indicate coupling between components A and B?

    • a. component A has access to the internal structure of component B.
    • b. components A and B are written by the same developer.
    • c. component A calls component B.
    • d. component A receives an object of component B as a parameter.
    • e. component A inherits from component B.
    • f. components A and B have to follow the same data format or communication protocol.

    (a)(b)(c)(d)(e)(f)

    Explanation: Being written by the same developer does not imply coupling.

    W5.5d :

    Design → Design Fundamentals → Coupling → Types of coupling

    Can identify types of coupling

    Some examples of different coupling types:

    • Content coupling: one module modifies or relies on the internal workings of another module e.g., accessing local data of another module
    • Common/Global coupling: two modules share the same global data
    • Control coupling: one module controlling the flow of another, by passing it information on what to do e.g., passing a flag
    • Data coupling: one module sharing data with another module e.g. via passing parameters
    • External coupling: two modules share an externally imposed convention e.g., data formats, communication protocols, device interfaces.
    • Subclass coupling: a class inherits from another class. Note that a child class is coupled to the parent class but not the other way around.
    • Temporal coupling: two actions are bundled together just because they happen to occur at the same time e.g. extracting a contiguous block of code as a method although the code block contains statements unrelated to each other


    Cohesion

    W5.5e :

    Design → Design Fundamentals → Cohesion → What

    Can explain cohesion

    Cohesion is a measure of how strongly-related and focused the various responsibilities of a component are. A highly-cohesive component keeps related functionalities together while keeping out all other unrelated things.

    Higher cohesion is better. Disadvantages of low cohesion (aka weak cohesion):

    • Lowers the understandability of modules as it is difficult to express module functionalities at a higher level.
    • Lowers maintainability because a module can be modified due to unrelated causes (reason: the module contains code unrelated to each other) or many modules may need to be modified to achieve a small change in behavior (reason: because the code related to that change is not localized to a single module).
    • Lowers reusability of modules because they do not represent logical units of functionality.

    W5.5f :

    Design → Design Fundamentals → Cohesion → How

    Can increase cohesion

    Cohesion can be present in many forms. Some examples:

    • Code related to a single concept is kept together, e.g. the Student component handles everything related to students.
    • Code that is invoked close together in time is kept together, e.g. all code related to initializing the system is kept together.
    • Code that manipulates the same data structure is kept together, e.g. the GameArchive component handles everything related to the storage and retrieval of game sessions.

    Suppose a Payroll application contains a class that deals with writing data to the database. If the class includes some code to show an error dialog to the user if the database is unreachable, that class is not cohesive because it seems to be interacting with the user as well as the database.

    Compare the cohesion of the following two versions of the EmailMessage class. Which one is more cohesive and why?

    // version-1
    class EmailMessage {
        private String sendTo;
        private String subject;
        private String message;
    
        public EmailMessage(String sendTo, String subject, String message) {
            this.sendTo = sendTo;
            this.subject = subject;
            this.message = message;
        }
    
        public void sendMessage() {
            // sends message using sendTo, subject and message
        }
    }
    
    // version-2
    class EmailMessage {
        private String sendTo;
        private String subject;
        private String message;
        private String username;
    
        public EmailMessage(String sendTo, String subject, String message) {
            this.sendTo = sendTo;
            this.subject = subject;
            this.message = message;
        }
    
        public void sendMessage() {
            // sends message using sendTo, subject and message
        }
    
        public void login(String username, String password) {
            this.username = username;
            // code to login
        }
    }
    

    Version 2 is less cohesive.

    Explanation: Version 2 is handling functionality related to login, which is not directly related to the concept of ‘email message’ that the class is supposed to represent. On a related note, you can improve the cohesion of both versions by removing the sendMessage functionality. Although sending messages is related to emails, this class is supposed to represent an email message, not an email server.

    [W5.6] Architecture

    Video

    W5.6a

    Design → Architecture → Introduction → What

    Can explain Software Architecture

    The software architecture of a program or computing system is the structure or structures of the system, which comprise software elements, the externally visible properties of those elements, and the relationships among them. Architecture is concerned with the public side of interfaces; private details of elements—details having to do solely with internal implementation—are not architectural. -- Software Architecture in Practice (2nd edition), Bass, Clements, and Kazman

    The software architecture shows the overall organization of the system and can be viewed as a very high-level design. It usually consists of a set of interacting components that fit together to achieve the required functionality. It should be a simple and technically viable structure that is well-understood and agreed-upon by everyone in the development team, and it forms the basis for the implementation.

    A possible architecture for a Minesweeper game:

    Main components:

    • GUI: Graphical user interface
    • TextUi: Textual user interface
    • ATD: An automated test driver used for testing the game logic
    • Logic: Computation and logic of the game
    • Store: Storage and retrieval of game data (high scores etc.)

    The architecture is typically designed by the software architect, who provides the technical vision of the system and makes high-level (i.e. architecture-level) technical decisions about the project.

    Choose the correct statement.

    • a. The architecture of a system should be simple enough for all team members to understand it.
    • b. The architecture is primarily a high-level design of the system.
    • c. The architecture is usually decided by the project manager.
    • d. The architecture can contain details private to a component.

    (a)(b)

    (c) Reason: Architecture is usually designed by the Architect.

    (d) Reason:

    ... private details of elements—details having to do solely with internal implementation—are not architectural.

    W5.6b

    Design → Architecture → Architecture Diagrams → Reading

    Can interpret an architecture diagram

    Architecture diagrams are free-form diagrams. There is no universally adopted standard notation for architecture diagrams. Any symbols that reasonably describe the architecture may be used.

    W5.6c

    Design → Architecture → Architecture Diagrams → Drawing

    Can draw an architecture diagram

    While architecture diagrams have no standard notation, try to follow these basic guidelines when drawing them.

    • Minimize the variety of symbols. If the symbols you choose do not have widely-understood meanings e.g. A drum symbol is widely-understood as representing a database, explain their meaning.

    • Avoid the indiscriminate use of double-headed arrows to show interactions between components.

    Consider the two architecture diagrams of the same software given below. Because Diagram 2 uses double-headed arrows, the important fact that GUI has a bidirectional dependency with the Logic component is no longer captured.

    W5.6d

    Design → Introduction → Multi-level design

    Can explain multi-level design

    In a smaller system, the design of the entire system can be shown in one place.

    This class diagram of se-edu/addressbook-level2 depicts the design of the entire software.

    The design of bigger systems needs to be done/shown at multiple levels.

    This architecture diagram of se-edu/addressbook-level3 depicts the high-level design of the software.

    Here are examples of lower level designs of some components of the same software:

    [W5.7] Architectural Styles

    Video

    W5.7a

    Design → Architecture → Styles → What

    Can explain architectural styles

    Software architectures follow various high-level styles (aka architectural patterns), just like how building architectures follow various architecture styles.

    n-tier style, client-server style, event-driven style, transaction processing style, service-oriented style, pipes-and-filters style, message-driven style, broker style, ...


    source: https://inspectapedia.com

    W5.7b

    Design → Architecture → Styles → n-Tier Style → What

    Can identify n-tier architectural style

    In the n-tier style, higher layers make use of services provided by lower layers. Lower layers are independent of higher layers. Other names: multi-layered, layered.

    Operating systems and network communication software often use n-tier style.

    W5.7c

    Design → Architecture → Styles → Client-Server Style → What

    Can identify the client-server architectural style

    The client-server style has at least one component playing the role of a server and at least one client component accessing the services of the server. This is an architectural style used often in distributed applications.

    The online game and the web application below use the client-server style.

    W5.7d

    Design → Architecture → Styles → Event-Driven Style → What

    Can identify event-driven architectural style

    Event-driven style controls the flow of the application by detecting An event is a notable occurrence that happens inside or outside the software, such as the user clicking a button, a timer running out, minimizing a window, etc.events from event emitters and communicating those events to interested event consumers. This architectural style is often used in GUIs.

    When the ‘button clicked’ event occurs in a GUI, that event can be transmitted to components that are interested in reacting to that event. Similarly, events detected at a printer port can be transmitted to components related to operating the printer. The same event can be sent to multiple consumers too.

    W5.7e

    Design → Architecture → Styles → Transaction Processing Style → What

    Can identify transaction processing architectural style

    The transaction processing style divides the workload of the system down to a number of transactions which are then given to a dispatcher that controls the execution of each transaction. Task queuing, ordering, undo etc. are handled by the dispatcher.

    In this example from a banking system, transactions are generated by the terminals used by employees of a bank who deal directly with customerstellers, which are then sent to a central dispatching unit, which in turn dispatches the transactions to various other units to execute.

    W5.7f : OPTIONAL

    Design → Architecture → Styles → Service-Oriented Style → What

    Can identify service-oriented architectural style

    The service-oriented architecture (SOA) style builds applications by combining functionalities packaged as programmatically accessible services. SOA aims to achieve interoperability between distributed services, which may not even be implemented using the same programming language. A common way to implement SOA is through the use of XML web services where the web is used as the medium for the services to interact, and XML is used as the language of communication between service providers and service users.

    Suppose that Amazon.com provides a web service for customers to browse and buy merchandise, while HSBC provides a web service for merchants to charge HSBC credit cards. Using these web services, an ‘eBookShop’ web application can be developed that allows HSBC customers to buy merchandise from Amazon and pay for them using HSBC credit cards. Because both Amazon and HSBC services follow the SOA architecture, their web services can be reused by the web application, even if all three systems use different programming platforms.

    W5.7g : OPTIONAL

    Design → Architecture → Styles → Using styles

    Can explain how architectural styles are combined

    Most applications use a mix of these architectural styles.

    An application can use a client-server architecture where the server component comprises several layers, i.e. it uses the n-tier architecture.

    Assume you are designing a multiplayer version of the Minesweeper game where any number of players can play the same Minefield. Players use their own PCs to play the game. A player scores by deducing a cell correctly before any of the other players do. Once a cell is correctly deduced, it appears as either marked or cleared for all players.

    Comment on how each of the following architectural styles could be potentially useful when designing the architecture for this game.

    1. Client-server
    2. Transaction-processing
    3. SOA (Service Oriented Architecture)
    4. multi-layer (n-tier)
    1. Client-server – Clients can be the game UI running on player PCs. The server can be the game logic running on one machine.
    2. Transaction-processing – Each player action can be packaged as transactions (by the client component running on the player PC) and sent to the server. Server processes them in the order they are received.
    3. SOA – The game can access a remote web service for things such as getting new puzzles, validating puzzles, charging players subscription fees, etc.
    4. Multi-layer – The server component can have two layers: the logic layer and the storage layer.

    W5.7h : OPTIONAL

    Design → Architecture → Styles → More styles

    Can name several other architecture styles

    Other well-known architectural styles include the pipes-and-filters architecture, the broker architecture, the peer-to-peer architecture, and the message-oriented architecture.

    [W5.8] Design Approaches

    Video

    W5.8a

    Design → Design Approaches → Top-down and bottom-up design

    Can explain top-down and bottom-up design

    Multi-level design can be done in a top-down manner, bottom-up manner, or as a mix.

    • Top-down: Design the high-level design first and flesh out the lower levels later. This is especially useful when designing big and novel systems where the high-level design needs to be stable before lower levels can be designed.
    • Bottom-up: Design lower level components first and put them together to create the higher-level systems later. This is not usually scalable for bigger systems. One instance where this approach might work is when designing a variation of an existing system or re-purposing existing components to build a new system.
    • Mix: Design the top levels using the top-down approach but switch to a bottom-up approach when designing the bottom levels.

    Top-down design is better than bottom-up design.

    False

    Explanation: Not necessarily. It depends on the situation. Bottom-up design may be preferable when there are a lot of existing components we want to reuse.

    W5.8b

    Design Approaches → Agile Design → Agile design

    Can explain agile design

    Agile design can be contrasted with full upfront design in the following way:

    Agile designs are emergent, they’re not defined up front. Your overall system design will emerge over time, evolving to fulfill new requirements and take advantage of new technologies as appropriate. Although you will often do some initial architectural modeling at the very beginning of a project, this will be just enough to get your team going. This approach does not produce a fully documented set of models in place before you may begin coding. -- adapted from agilemodeling.com

    The agile design camp expects the design to change over the product’s lifetime.

    True

    Explanation: Yes, that is why they do not believe in spending too much time creating a detailed and full design at the very beginning. However, the architecture is expected to remain relatively stable even in the agile design approach.