This site is from a past semester! The current version is here.
CS2113/T Aug '19
  • Week 1 [Aug 12]
  • Week 2 [Aug 19]
  • Week 3 [Aug 26]
  • Week 4 [Sep 2]
  • Week 5 [Sep 9]
  • Week 6 [Sep 16]
  • Week 7 [Sep 30]
  • Week 8 [Oct 7]
  • Week 9 [Oct 14]
  • Week 10 [Oct 21]
  • Week 11 [Oct 28]
  • Week 12 [Nov 4]
  • Week 13 [Nov 11]
  • Textbook
  • Admin Info
  • Report Bugs
  • Slack
  • Forum
  • Project Info
  • Instructors
  • Announcements
  • File Submissions
  • Tutorial Schedule
  • Duke
  • Project Phase1 Dashboard
  • Java Coding Standard
  • samplerepo-things
  • Projects List
  • config.json templates for Reposense
  • PersonalAssistant-Duke
  • Project Phase2 Dashboard
  • Reference project - Addressbook
  • Repl.it classroom
  • Previous WeekNext Week

    Week 6 [Sep 16]

    • [W6.1] Continuous Integration/Deployment
    • [W6.1a] Implementation → Integration → Introduction → What

    • [W6.1b] Implementation → Integration → Build Automation → What

    • [W6.1c] Implementation → Integration → Build Automation → Continuous Integration and Continuous Deployment

    • [W6.2] CodeQuality

       Readability

    • [W6.2a] Implementation → Code Quality → Readability → Introduction

    • [W6.2b] Implementation → Code Quality → Readability → Basic → Avoid Long Methods

    • [W6.2c] Implementation → Code Quality → Readability → Basic → Avoid Deep Nesting

    • [W6.2d] Implementation → Code Quality → Readability → Basic → Avoid Complicated Expressions

    • [W6.2e] Implementation → Code Quality → Readability → Basic → Avoid Magic Numbers

    • [W6.2f] Implementation → Code Quality → Readability → Basic → Make the Code Obvious

    • [W6.2g] Implementation → Code Quality → Readability → Intermediate → Structure Code Logically

    • [W6.2h] Implementation → Code Quality → Readability → Intermediate → Do Not 'Trip Up' Reader

    • [W6.2i] Implementation → Code Quality → Readability → Intermediate → Practice KISSing

    • [W6.2j] Implementation → Code Quality → Readability → Intermediate → Avoid Premature Optimizations

    • [W6.2k] Implementation → Code Quality → Readability → Intermediate → SLAP Hard

    • [W6.2l] Implementation → Code Quality → Readability → Advanced → Make the Happy Path Prominent

       Naming

    • [W6.2m] Implementation → Code Quality → Naming → Introduction

    • [W6.2n] Implementation → Code Quality → Naming → Basic → Use Nouns for Things and Verbs for Actions

    • [W6.2o] Implementation → Code Quality → Naming → Basic → Use Standard Words

    • [W6.2p] Implementation → Code Quality → Naming → Intermediate → Use Name to Explain

    • [W6.2undefined] Implementation → Code Quality → Naming → Intermediate → Not Too Long, Not Too Short

    • [W6.2undefined] Implementation → Code Quality → Naming → Intermediate → Avoid Misleading Names

       Unsafe Practices

    • [W6.2undefined] Implementation → Code Quality → Error-Prone Practices → Introduction

    • [W6.2undefined] Implementation → Code Quality → Error-Prone Practices → Basic → Use the Default Branch

    • [W6.2undefined] Implementation → Code Quality → Error-Prone Practices → Basic → Don't Recycle Variables or Parameters

    • [W6.2undefined] Implementation → Code Quality → Error-Prone Practices → Basic → Avoid Empty Catch Blocks

    • [W6.2undefined] Implementation → Code Quality → Error-Prone Practices → Basic → Delete Dead Code

    • [W6.2undefined] Implementation → Code Quality → Error-Prone Practices → Intermediate → Minimize Scope of Variables

    • [W6.2undefined] Implementation → Code Quality → Error-Prone Practices → Intermediate → Minimize Code Duplication

       Code Comments

    • [W6.2undefined] Implementation → Code Quality → Comments → Introduction

    • [W6.2undefined] Implementation → Code Quality → Comments → Basic → Do Not Repeat the Obvious

    • [W6.2undefined] Implementation → Code Quality → Comments → Basic → Write to the Reader

    • [W6.2undefined] Implementation → Code Quality → Comments → Intermediate → Explain WHAT and WHY, not HOW

    • [W6.3] IDEs: Basic refactoring
    • [W6.3a] Implementation → Refactoring → What

    • [W6.3b] Tools → Intellij IDEA → Refactoring

    • [W6.3c] Implementation → Refactoring → How

    • [W6.3d] Implementation → Refactoring → When

    • [W6.4] IDEs: Intermediate features
    • [W6.4a] Implementation → IDEs → Debugging → What

    • [W6.4b] Tools → Intellij IDEA → Debugging: Basic

    • [W6.4c] Tools → Intellij IDEA → Productivity Shortcuts

    • [W6.5] UML: Sequence Diagrams - Basics
    • [W6.5a] Design → Modelling → Modelling Behaviors Sequence Diagrams - Basic


    [W6.1] Continuous Integration/Deployment

    W6.1a Implementation → Integration → Introduction → What

    Can explain integration

    Combining parts of a software product to form a whole is called integration. It is also one of the most troublesome tasks and it rarely goes smoothly.

    W6.1b Implementation → Integration → Build Automation → What

    Can explain build automation tools

    Build automation tools automate the steps of the build process, usually by means of build scripts.

    In a non-trivial project, building a product from source code can be a complex multi-step process. For example, it can include steps such as to pull code from the revision control system, compile, link, run automated tests, automatically update release documents (e.g. build number), package into a distributable, push to repo, deploy to a server, delete temporary files created during building/testing, email developers of the new build, and so on. Furthermore, this build process can be done ‘on demand’, it can be scheduled (e.g. every day at midnight) or it can be triggered by various events (e.g. triggered by a code push to the revision control system).

    Some of these build steps such as to compile, link and package are already automated in most modern IDEs. For example, several steps happen automatically when the ‘build’ button of the IDE is clicked. Some IDEs even allow customization to this build process to some extent.

    However, most big projects use specialized build tools to automate complex build processes.

    Some popular build tools relevant to Java developers: Gradle, Maven, Apache Ant, GNU Make

    Some other build tools : Grunt (JavaScript), Rake (Ruby)

    Some build tools also serve as dependency management tools. Modern software projects often depend on third party libraries that evolve constantly. That means developers need to download the correct version of the required libraries and update them regularly. Therefore, dependency management is an important part of build automation. Dependency Management tools can automate that aspect of a project.

    Maven and Gradle, in addition to managing the build process, can play the role of dependency management tools too.

    Gradle_is used used for,

    • a. better revision control
    • b. build automation
    • c. UML diagramming
    • d. project collaboration

    (b)

    W6.1c Implementation → Integration → Build Automation → Continuous Integration and Continuous Deployment

    Can explain continuous integration and continuous deployment

    An extreme application of build automation is called continuous integration (CI) in which integration, building, and testing happens automatically after each code change.

    A natural extension of CI is Continuous Deployment (CD) where the changes are not only integrated continuously, but also deployed to end-users at the same time.

    Some examples of CI/CD tools: Travis, Jenkins, Appveyor, CircleCI

    [W6.2] CodeQuality


    Readability

    W6.2a 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 we do not intend to pass the code to someone else, code quality is still important because we all become 'strangers' to our 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 (isAlreadySubsidised) {
            subsidy = SUBSIDISED_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_subsidised:
            return SUBSIDISED_SUBSIDY
        elif is_parttime:
            return FULLTIME_SUBSIDY * RATIO
        else:
            return FULLTIME_SUBSIDY
    

    W6.2b 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.

    W6.2c 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 CodingStyle

    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 (isAlreadySubsidised) {
            subsidy = SUBSIDISED_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_subsidised:
            return SUBSIDISED_SUBSIDY
        elif is_parttime:
            return FULLTIME_SUBSIDY * RATIO
        else:
            return FULLTIME_SUBSIDY
    

    W6.2d 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

    W6.2e 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, we call that a "magic number" (as in "the number appears as if by magic"). Using a named 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, we can have ‘magic’ values of other data types.

    Bad

    "Error 1432"  // A magic string!
    

    In general, try to avoid any magic literals.

    W6.2f 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 grouping 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'.

    W6.2g 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 we 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.

    W6.2h 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 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

    W6.2i 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

    W6.2j Implementation → Code Quality → Readability → Intermediate → Avoid Premature Optimizations

    Can improve code quality using technique: avoid premature optimizations

    Optimizing code prematurely has several drawbacks:

    • We 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 for the compiler to optimize it). 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 on matter how fast/efficient it it.

    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 simply a caution that you should optimize only when it is really needed.

    W6.2k 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 Productive Programmer (by Neal Ford) calls this the SLAP principle i.e. Single Level of Abstraction Per method.

    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 needs 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, we 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 abstractions.

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

    W6.2l 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 detection is separated from their handling.
    • 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.

    Naming

    W6.2m Implementation → Code Quality → Naming → Introduction

    Can explain the need for good names in code

    Proper naming improves the readability. 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

    W6.2n 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 multivalued variables.

    Examples:

    Good

    Person student;
    ArrayList<Person> students;
    

    Good

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

    W6.2o 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

    W6.2p 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 the 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

    W6.2undefined 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.

    W6.2undefined 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 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

    Unsafe Practices

    W6.2undefined 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. Some such coding practices are common sources of bugs. Know them and avoid them.

    W6.2undefined 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, throw an exception). 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");
    

    W6.2undefined 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;
    }
    
    

    Good

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

    W6.2undefined 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.

    W6.2undefined Implementation → Code Quality → Error-Prone Practices → Basic → Delete Dead Code

    Can improve code quality using technique: delete dead code

    We all feel reluctant to delete code we have painstakingly written, even if we have no use for that code any more ("I spent a lot of time writing that code; what if we 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.

    W6.2undefined Implementation → Code Quality → Error-Prone Practices → Intermediate → Minimize Scope of Variables

    Can improve code quality using technique: minimise 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:

    W6.2undefined 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 duplication of information.

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

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


    Code Comments

    W6.2undefined 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'. This is not so. Avoid writing comments to explain bad code. Improve the code to make it self-explanatory.

    W6.2undefined 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()
    

    W6.2undefined 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 comments 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

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

    Good

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

    W6.2undefined 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 what and why aspect of the code, rather than the how aspect.

    What : The specification of what the code 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;
    

    [W6.3] IDEs: Basic refactoring

    W6.3a 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 built in 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 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 we refactor code to fix bugs, we 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. We MUST do regression testing after each change, no matter how minor it is.
    2. Fixing bugs is technically not refactoring.

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

    W6.3b Tools → Intellij IDEA → Refactoring

    Can use automated refactoring features of the IDE

    This video explains how to automate the 'Extract parameter' refactoring using Intellij IDEA. Most other refactorings available works 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 change a method signature as part of refactoring.

    W6.3c 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

    W6.3d Implementation → Refactoring → When

    Can decide when to apply a given refactoring

    We 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

    [W6.4] IDEs: Intermediate features

    W6.4a Implementation → IDEs → Debugging → What

    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 increases 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 difficult, time consuming, and error-prone technique.
      • If you didn't spot the error while writing code, you might not spot the error when reading code too.
    • Good -- Using a debugger: A debugger tool allows you to pause the execution, then step through 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.

    W6.4b 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.

    W6.4c Tools → Intellij IDEA → Productivity Shortcuts

    Can use some useful IDE productivity shortcuts

    [W6.5] UML: Sequence Diagrams - Basics

    W6.5a Design → Modelling → Modelling Behaviors Sequence Diagrams - Basic

    Can draw basic sequence diagrams

    Explain in your own words the interactions illustrated by this Sequence Diagram:

    Consider the code below:

    class Person{
        Tag tag;
        String name;
    
        Person(String personName, String tagName){
            name = personName;
            tag = new Tag(tagName);
        }
    }
    
    class Tag{
        Tag(String value){
            //...
        }
    }
    
    class PersonList{
        void addPerson(Person p){
            //...
        }
    }
    

    Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:

    PersonList personList = new PersonList();
    while (hasRoom){
        Person p = new Person("Adam", "friend");
        personList.addPerson(p);
    }
    

    Find notation mistakes in the sequence diagram below: