Programs are written for humans to read and only occasionally executed by computers. – Donald Ervin Knuth

sequence

Every time review past written code, always have a kind of unbearable feeling. I want to improve my coding ability, so I read some related books and blog posts, and have some feelings. Now I will sort out some reading notes and personal feelings. I hope this brick can be thrown up.

The outline

  • Bad taste code
  • Cyclomatic complexity
  • refactoring
  • Clean code
  • Coding principles & Design patterns
  • conclusion
  • reference

Bad taste code

Before you start reading, think quickly, what are your images of good code and bad code?

If you see this piece of code, how do you evaluate it?

if(a && d || b && c && ! d || (! a || ! b) && c) {// ...
} else {
  // ...
}
Copy the code

The above code, although written specifically as an example, would be a long story if it were true. We all have some “impression” of bad-smelling code, and bad-smelling code always has some commonalities:

  • Duplicated Code
  • Long Method (Long Method)
  • Large Class = Large Class
  • Long Parameter List
  • Temporary Field (Temporary Field)
  • Shotgun Surgery: A change causes multiple classes to change accordingly
  • .

So how does bad-smelling code come into being?

  • The last programmer to write this code was inexperienced, incompetent, or didn’t write it hard enough;
  • Weird requirements from product managers lead to a lot of hack code;
  • The business of a particular module is too complex, the requirements change too many times, and too many programmers handle it.
  • .

With an overview of bad-smelling code, the reader may have a question in his mind: Is there any quantitative criteria to determine whether code is good or bad? The answer is yes.

Next, measure the code we write by understanding cyclomatic complexity. However, when the bad smell of code is everywhere, it’s time to look at refactoring. When the code is in our hands, we should know how to write clean code. In addition, there are some coding principles and design patterns that we need to know so that we can focus on what we want to do.

Cyclomatic complexity

Cyclomatic complexity (CC), also known as conditional complexity, is a measure of code complexity. Proposed by Thomas J. McCabe (Sr.) in 1976, it is used to represent the complexity of a program.

Cyclomatic complexity can be used to measure the complexity of a module’s decision structure, which is represented by the number of independent current paths and can also be understood as the number of least used test cases covering all possible cases.

methods

Cyclomatic complexity can be calculated by program control flow diagram. The formula is:

V(G) = e + 2 – n

  • E: Controls the number of edges in the flow diagram
  • N: indicates the number of nodes in the control flow diagram

Here’s a simple calculation: cyclomatic complexity is actually equal to the number of decision nodes plus one.

Note: if the else, switch case, the for loop, the ternary operator, | |, &, etc., all belong to a decision node.

Measure of the

Low code complexity is not necessarily good code, but high code complexity is definitely bad code.

Cyclomatic complexity The code in measurability Maintenance costs
1-10 Clear and structured high low
10-20 complex In the In the
20-30 Very complicated low high
> 30 Do not read unpredictable Very high

Cyclomatic complexity detection method

ESLint rules

ESLint provides rules that detect code cyclomatic complexity. Turn on the complexity rule in Rules and set the rule severity for code with cyclomatic complexity greater than 0 to WARN or Error.

rules: {
    complexity: [
        'warn',
        { max: 0 }
    ]
}
Copy the code

CLIEngine

Using ESLint’s CLIEngine, you scan code locally using custom ESLint rules and get the scan output.

Reduce the cyclomatic complexity of code

In many cases, reducing cyclomatic complexity improves code readability. Some suggestions for improving cyclomatic complexity are given with examples:

1. Abstract configuration

Complex logical decisions are simplified by abstract configuration.

before:

// ...
if (type === 'scan') {
    scan(args)
} else if (type === 'delete') {
    delete(args)
} else if (type === 'set') {
    set(args)
} else {
    // ...
}
Copy the code

after:

constACTION_TYPE = {scan: scan, delete:delete} ACTION_TYPE[type](args)Copy the code

2. Refine functions

Abstractions of the logic in your code into individual functions can help reduce code complexity and maintenance costs. Especially if the code for a function is long and laborious to read, you should consider distilling it into multiple functions.

before:

function example(val) {
    if (val > MAX_VAL) {
        val = MAX_VAL
    }
    for (let i = 0; i < val; i++) {
        doSomething(i)
    }
    // ...
}
Copy the code

after:

function setMaxVal(val) {
    return val > MAX_VAL ? MAX_VAL : val
}

function getCircleArea(val) {
    for (let i = 0; i < val; i++) {
        doSomething(i)
    }
}
function example(val) {
    return getCircleArea(setMaxVal(val))
}
Copy the code

3. Reverse condition simplifies condition judgment

Some complex conditional judgments can be made simpler and less nested by reverse thinking.

before:

function checkAuth(user){
    if (user.auth) {
        if (user.name === 'admin') {
            // ...
        } else if (user.name === 'root') {
            // ...}}}Copy the code

after:

function checkAuth(user){
    if(! user.auth)return
    if (user.name === 'admin') {
        // ...
    } else if (user.name === 'root') {
        // ...}}Copy the code

4. Merge conditions to simplify conditional judgment

The redundant conditions are combined and then judged.

before:

if (fruit === 'apple') {
    return true
} else if (fruit === 'cherry') {
    return true
} else if (fruit === 'peach') {
    return true
} else {
    return true
}
Copy the code

after:

const redFruits = ['apple'.'cherry'.'peach']
if (redFruits.includes(fruit) {
    return true
}
Copy the code

5. Extract conditions to simplify condition judgment

Extract and semantize complex conditions.

before:

if ((age < 20 && gender === 'woman') || (age > 60 && gender === 'male')) {
    // ...
} else {
    // ...
}
Copy the code

after:

function isYoungGirl(age, gender) {
    return (age < 20 && gender === 'woman'
}
function isOldMan(age, gender) {
    return age > 60 && gender === 'male'
}
if (isYoungGirl(age, gender) || isOldMan(age, gender)) {
    // ...
} else {
    // ...
}
Copy the code

A more comprehensive summary of simplified conditional expressions is provided later.

refactoring

The word reconstruction has both a noun and a verb meaning. Noun:

An adjustment of the internal structure of software to improve its understandability and reduce its modification cost without changing the observable behavior of software.

Verb:

A series of refactoring techniques are used to adjust the structure of the software without changing its observable behavior.

Why does refactoring

You might want to think about refactoring if you encounter the following situations:

  • Too much duplicated code
  • The structure of the code is chaotic
  • The program is not extensible
  • The object structure is strongly coupled
  • The performance of some modules is low

The reasons for refactoring are as follows:

  • Refactoring improves software design
  • Refactoring makes the software easier to understand
  • Refactoring helps find bugs
  • Refactoring speeds up programming

Types of refactoring:

  • Code level refactoring of existing projects;
  • Upgrade the software architecture and system of existing services.

This article covers only the first point and is limited to code level refactoring.

Reconstruction time

The first time you do something, do it. The second time you do something like this, you’ll resent it, but you can do it anyway; The third time you do something similar, you refactor.

  • When adding features: When adding new features, if a piece of code is found to be particularly difficult to change or difficult to extend, refactor that piece of code to make it easier to add new features and features;
  • Repair errors: when you change a bug or locating problems, found himself before writing the code or the code design of others defects (e.g., not flexible scalability), or robustness enough considerate (such as omitting some of this processing abnormal), leading to frequent problems, so at this time is a better way to reconstruct the timing;
  • When reviewing Code: When the team conducts a Code Review, it is also an appropriate time to refactor.

Clean code

The key idea

  • The code should be easy to understand;
  • Code should be written in such a way as to minimize the time it takes for others to understand it.

Code style.

Key idea: Consistent style is more important than “right” style.

Principle:

  • Use a consistent layout
  • Make similar code look similar
  • Groups related lines of code into blocks

annotation

The purpose of annotations is to try to help the reader understand as much as the writer. Therefore annotations should have a high information/space rate.

No comments required

  • Don’t comment for the sake of commenting
  • Don’t comment bad names (should have good names)
  • Don’t comment on facts that can be quickly inferred from the code itself

I need to write a comment

  • Intrinsic reasons for why code is written this way and not that way (instructional comments)
  • The story behind the constant, why is it this value
  • Annotate the reader’s unexpected behavior
  • Use “big Picture” comments at the file/class level to explain how all the pieces work together
  • Use comments to summarize blocks of code so the reader doesn’t get lost in the details
  • Describe the behavior of the function as accurately as possible
  • Declare the high-level intent of the code, rather than the obvious details
  • Use input/output examples in the comments
  • Defects in the code, use tags
tag General meaning
TODO: Things that haven’t been dealt with yet
FIXME: Known code that doesn’t work
HACK: A crude solution that has to be applied to a problem

named

Key idea: Put information in a name.

Good naming is a way to get code readable “at a low cost.”

Choose terms

“Putting information in your name” includes choosing very specialized words and avoiding “empty” words.

The word More choice
send deliver, despatch, announce, distribute, route
find search, extract, locate, recover
start launch, create, begin, open
make create, set up, build, generate, compose, add, new

Avoid generic names like TMP and retval

  • The name Retval doesn’t contain much information
  • TMP is only applied to variables that are short-lived and whose temporality is the primary factor in their existence

Use concrete names instead of abstract names

When naming a variable, function, or other element, make it more concrete rather than abstract.

Add more information to the name

If there is something important about a variable that the reader must know, then it is worth adding an extra “word” to the name.

Length of name

  • You can use short names in small scopes
  • Use longer names for large-scoped names
  • Discard useless words

A name that will not be misunderstood

  • The limit is expressed in terms of min and Max
  • Use first and last to indicate the scope of inclusion
  • Use begin and end to indicate the exclusion range
  • Name the Boolean values: is, has, can, should

Words with opposite meaning come in pairs

is the
add remove
create destory
insert delete
get set
increment decrement
show hide
start stop

Other naming tips

  • Calculate qualifiers as prefixes or suffixes (Avg, Sum, Total, Min, Max)
  • The variable name should be able to accurately represent the meaning of the thing
  • Use gerunds to name functions
  • Abbreviations for variable names. Avoid unusual abbreviations

Reduced conditional expression

Decomposing conditional expressions

There is a complex condition (if-then-else) statement that extracts separate functions from the if, THEN, and else paragraphs. Depending on the purpose of each small piece of code, give the new function a name and change the corresponding code in the original function to call the new function to more clearly express your intention. For conditional logic, you can highlight the conditional logic, make it clearer what each branch does, and highlight the reason for each branch.

Merge conditional expressions

There’s a series of conditional tests, all getting the same result. Combine these tests into a single conditional expression, and refine the conditional expression into a stand-alone function.

  • Make sure these conditional statements have no side effects;
  • Combine a list of related conditional expressions into one, using the appropriate logical operators;
  • Implement the extract function on the merged conditional expression.

Merge repeated conditional fragments

Have the same piece of code on each branch of the conditional expression, and move this piece of repeated code outside of the conditional expression.

Replace nested conditional expressions with health statements

Conditional logic in functions makes it difficult to see the normal execution path. Use guard statements to represent all special cases.

If a condition is extremely rare, it should be checked separately and returned from the function as soon as it is true. Such individual checks are often referred to as guard clauses.

It is often possible to reverse a conditional expression so that instead of nesting a conditional expression, you can actually write more “linear” code to avoid deep nesting.

Variables and readability

Problems with variables:

  • The more variables there are, the harder it is to track them all
  • The larger the scope of a variable, the longer it needs to be tracked
  • The more frequently a variable changes, the harder it is to keep track of its current value

Inline temporary variables

If you have a temporary variable that is assigned only once by a simple expression, replace all references to that variable with the expression that assigned it.

Replace temporary variables with queries

Hold the result of an expression as a temporary variable and extract the expression into a separate function. Replace all reference points to the temporary variable with calls to the new function. After that, the new function can be used by other functions.

Conclusion the variable

If the expression is more complex, it is recommended to use a summary variable name instead of a large chunk of code, which is easier to manage and think about.

Introduce explanatory variables

Put the result of a complex expression (or part of one) into a temporary variable, using the variable name to explain the purpose of the expression.

In conditional logic, the introduction of explanatory variables is particularly valuable: each conditional clause can be distilled into a well-named temporary variable that explains the meaning of the corresponding conditional clause. Another way to use this refactoring is to use temporary variables to explain the meaning of each step in a longer algorithm.

Benefits:

  • Break large expressions into smaller pieces
  • Document the code by describing the subexpressions with simple names
  • Helps the reader identify the main concepts in the code

Decomposing temporary variables

The program has a temporary variable assigned more than once, which is neither a loop variable nor used to collect the results of a calculation. Create a separate, corresponding temporary variable for each assignment.

Temporary variables serve a variety of purposes:

  • Loop variables
  • Result collection variables (collect a value of the whole function through the operation)

If a temporary variable has multiple responsibilities, it should be replaced (decomposed) into multiple temporary variables, each with only one responsibility.

Replace Magic Number with literal constant

It has a literal value with a special meaning. Create a constant, name it according to its meaning, and replace the literal value above with this constant.

Reduce control flow variables

let done = false;

while(condition && ! done) {if(...). { done =true;
        continue; }}Copy the code

Variables like DONE are called “control flow variables.” Their sole purpose is to control the execution of the program and contain no program data. Control flow variables can often be eliminated by better use of structured programming.

while (condition) {
    if(...). {break; }}Copy the code

If there are multiple nested loops and a simple break is not enough, the usual solution involves moving the code into a new function.

Reorganization function

Refining function

When a function is too long or a piece of code needs comments to make sense of its purpose, put the code in a separate function.

  • If the granularity of the function is small, the chance of reuse is great.
  • Functions have smaller granularity and are easier to overwrite.

Is a function too long? Length is not the issue, but the semantic distance between the function name and the function ontology is the key.

Separate query functions from modify functions

A function that both returns an object state value and modifies the object state. Create two different functions, one for querying and one for modifying.

Replace parameters with explicit functions

There is a function that behaves differently depending entirely on the parameter value. Create a separate function for each possible value of the parameter.

Import parameter objects

Certain parameters are always naturally present at the same time, replacing them with an object.

Returns ahead of time from a function

You can do this by handling the “special case” right away and returning it early from the function.

Break up large statements

If you have hard-to-read code, try listing all the tasks it does. Some of these tasks can easily become separate functions (or classes). The rest can simply be logical “paragraphs” in a function.

Other suggestions and ideas

Reduce nesting within loops

In a loop, a similar technique to returning early is continue. With the if… return; In function protection, if… continue; A statement is a protection statement in a loop. (Note the peculiarity of forEach in JavaScript)

De Morgan’s law

There are two equivalent ways to write a Boolean expression:

  • not (a or b or c) <=> (not a) and (not b) and (not c)
  • not (a and b and c) <=> (not a) or (not b) or (not c)

You can use these rules to make Boolean expressions more readable. Such as:

// before
if(! (file_exitsts && ! is_protected)) Error("sorry, could not read file.")

// after
if(! file_exists || is_protected) Error("sorry, could not read file.")
Copy the code

Use relevant laws to optimize the code we started with:

// before
if(a && d || b && c && ! d || (! a || ! b) && c) {// ...
} else {
  // ...
}

// after
if (a && d || c) {
  // ...
} else {
  // ...
}
Copy the code

To simplify the process and the laws involved, check out this tweet: “If you write code like this, you won’t be afraid of being beaten up.”

Reorganize the code

Engineering is all about breaking big problems down into smaller problems and putting the solutions back together.

Applying this principle to your code makes it more robust and easier to read.

Actively discovering and extracting irrelevant sublogic means:

  • Look at a function or block of code and ask yourself: What is the high-level goal of this code?
  • For each line of code, ask: Does it work directly to the goal?
  • If there are enough lines to solve an unrelated subproblem, extract the code into a separate function.

Turn ideas into code

If you can’t explain something to your grandmother, you don’t really understand it. — Albert Einstein

The steps are as follows:

  1. Describe the code in natural language as if it were a colleague
  2. Pay attention to the key words and phrases used in the description
  3. Write the code that matches the description

Coding principles & Design patterns

It is necessary to familiarize ourselves with some classical coding principles and related patterns summarized by predecessors in order to improve our existing coding habits, the so-called “programming on the shoulders of giants”.

The principle of SOLID

SOLID is an acronym for the five basic principles of Object Oriented Design (OOD), introduced by Robert C.Martin, commonly known as “Uncle Bob”, in agile Software Development: Principles, Patterns, and Practices.

  • S(Single Responsibility Principle) : SRP
  • O: Open Close Principle (OCP)
  • L(Liskov Substitution Principle) : LSP
  • Interface isolation Principle (I) : Interface isolation principle (ISP)
  • D(Dependence Inversion Principle) : Dependence Inversion Principle, or DIP

Single responsibility principle

A class should have only one reason to change.

A class should have only one reason for it to change.

In general terms: a class is responsible for only one function or a similar class of functions. Of course, this “one” is not absolute. It should be understood that a class is responsible for only one function as independent as possible, with as few responsibilities as possible.

Advantages:

  • Single function, clear responsibilities.
  • Improved readability and easy maintenance.

Disadvantages:

  • Break it down too much, and the number of classes increases dramatically.
  • There is no uniform measure of responsibility and it depends on project implementation.

This law also applies to the coding principle when organizing functions.

Open and closed principle

Software entities (classes, modules, functions provides, etc.) should be openfor extension, but closed for modification.

Software entities (such as classes, modules, functions, etc.) should be open to extension, but closed to modification.

During the life cycle of a software product, there will inevitably be some changes in business and requirements, and we should consider these changes as much as possible when designing the code. When adding a feature, you should leave existing code untouched as much as possible. Changes to one module should not affect other modules.

const makeSound = function( animal ) { 
    animal.sound(); 
};

const Duck = function(){}; 
Duck.prototype.sound = function(){ 
    console.log( 'Quack quack' ); 
};

const Chicken = function(){}; 
Chicken.prototype.sound = function(){ 
    console.log( 'Gurgling.' ); 
}; 

makeSound( new Duck() ); / / ga ga ga
makeSound( new Chicken() ); / / luo luo luo
Copy the code

Richter’s substitution principle

Functions that use pointers to base classes must be able to useobjects of derived classes without knowing it.

All references to the base class must transparently use the objects of its subclasses.

A subclass can appear wherever the superclass can (and can be replaced with a subclass). Conversely, where a subclass can appear, a superclass does not necessarily appear (the subclass has all the properties and behaviors of the superclass, but the subclass extends more functionality).

Interface isolation Rule

Clients should not be forced to depend upon interfaces that they don't use.Instead of one fat interface many small interfaces arepreferred based on groups of methods,each one serving onesubmodule.

A client should not rely on interfaces it does not need. Instead of a complex interface consisting of multiple methods, each interface serves a submodule, use multiple fine-grained interfaces.

The interface should be small, but limited. When an interface is found to be bloated, the interface should be split appropriately. However, if the number of interfaces is too small, the design will be complicated.

Principle of dependence inversion

High level modules should not depend on low level modules; bothshould depend on abstractions.Abstractions should not depend ondetails.Details should depend upon abstractions.

High-level modules should not depend on low-level modules; both should depend on their abstractions. Abstractions should not depend on details; details should depend on abstractions.

To have the same characteristics or similar function of the class, abstract into an interface or abstract class, let the concrete implementation class inheritance of this abstract class (or implementation of the corresponding interface). Abstract classes (interfaces) are responsible for defining unified methods, and implementation classes are responsible for implementing specific functions.

Is it necessary to follow these design principles

  • Software design is a process of gradual optimization
  • You don’t have to follow these design principles

There is not enough time to design according to these principles, or the implementation cost of designing according to these principles is too high. When we can’t follow the five principles due to the constraints of reality, we can follow these simpler and more practical principles.

Simple, practical principles

LoD Principle (Law of Demeter)

Each unit should have only limited knowledge about other units: onlyunits “closely”related to the current unit.Only talk to your immediatefriends,don't talk to strangers.

Each LUS should have minimal knowledge of the other LUs: that is, close only to the current object. Only talk to direct (close) friends, not strangers.

This principle, also known as Demeter’s Law, simply states that a class knows as little as possible about the classes it depends on, and that it only needs to interact with the immediate object, regardless of its internal structure.

For example, if class A has an object of class B, class B has an object of class C, and the caller has an object A of class A, if you want to access properties of A C object, do not write something like this:

a.getB().getC().getProperties()
Copy the code

It should be:

a.getProperties()
Copy the code

KISS principle (Keep It Simple and Stupid)

Keep It Simple and Stupid.

Keep it simple and stupid.

  • “Simple” means that your program can be implemented easily and quickly;
  • “Stupid” means your design should be simple enough for anyone to understand. Simple is beautiful!

DRY (Don’t Repeat Yourself)

DRY (Don’t Repeat Yourself)

Don’t repeat yourself.

Don’t duplicate your code, that is, run into the same problem more than once, abstract a common solution, and don’t develop the same functionality over and over. This means maximizing the reuse rate of your code.

There are many ways to follow the DRY principle:

  • Function-level encapsulation: The encapsulation of frequently used, recurring functions into a common function.
  • Class-level abstraction: Classes with similar functionality or behavior are abstracted into a base class, and the methods of each class are implemented in the base class.
  • Generic design: Generics can be used in Java to achieve common functional classes to support multiple data types; C++ can use class templates, or macros; Decorators can be used in Python to eliminate redundant code.

DRY principles are easier to follow and implement when working with a single person than when working with a team, especially on a large team project, where communication is key.

YAGNI Principle (You Aren’t Gonna Need It)

You aren't gonna need it,don't implement something until it isnecessary.

You don’t have to rush. Don’t give your class too much functionality until you need it.

  • Consider and design only the necessary features and avoid overdesigning.
  • Only implement what you need now, and you can add more later when you need more.
  • Don’t add complexity if you don’t need to.

Rule Of Three

The Rule of three is called the “Rule of three”, which means that when a feature appears for a third time, the function is abstracted again, and the third time is refactoring.

  • The first time you implement a feature, go for it.
  • The second time to do a similar function design will be disgusted, but still have to do it;
  • The third time you want to implement the same function and do the same thing, you should look at whether it is necessary to redo the work. At this time, you should refactor your code, that is, to abstract the code with the same or similar function and encapsulate it into a common module or interface.

CQS (Command-Query Separation)

  • Query: When a method returns a value in response to a question, it has the property of a Query;
  • Command: When a method changes the state of an object, it has the nature of a Command.

Ensure that the method behaves strictly as a command or query, so that the query method does not change the object’s state and has no side effects; Methods that change the state of an object cannot return a value.

Design patterns

In his book Design Patterns: The Foundation of Reusable Object-oriented Software, GoF, the founder of design patterns, proposed 23 classic design patterns, which are divided into three categories: creation patterns, structure patterns, and behavior patterns.

A simple and elegant solution to a specific problem in object-oriented software design.

Common design patterns include policy pattern, publish – subscribe pattern, chain of responsibility pattern and so on.

For example, the scenario where the policy mode is used:

Strategy pattern: Define a set of algorithms, encapsulate them one by one, and make them interchangeable.

if (account === null || account === ' ') {
    alert('Phone number cannot be empty');return false;
}
if (pwd === null || pwd === ' ') {
    alert('Password cannot be empty');return false;
}
if (!/ (a ^ 1 [3 4 5 7 | | | | 8] [0-9] {9} $) /.test(account)) {
    alert('Incorrect format of phone number');return false;
}
if(pwd.length<6){
    alert('Password must not be less than six characters');return false;
}
Copy the code

Using policy mode:

const strategies = {
    isNonEmpty: function (value, errorMsg) {
        if (value === ' ' || value === null) {
            returnerrorMsg; }},isMobile: function (value, errorMsg) { // Mobile number format
        if (!/ (a ^ 1 [3 4 5 7 | | | | 8] [0-9] {9} $) /.test(value)) {
            returnerrorMsg; }},minLength: function (value, length, errorMsg) {
        if (value.length < length) {
            returnerrorMsg; }}};const accountIsMobile = strategies.isMobile(account,'Incorrect format of phone number');
const pwdMinLength = strategies.minLength(pwd,8.'Password must not be less than 8 characters');
const errorMsg = accountIsMobile || pwdMinLength; 
if (errorMsg) {
    alert(errorMsg);
    return false;
}
Copy the code

Another example is the features of the publish-subscribe model:

  • Decoupling in time
  • Decoupling between objects

It can be used in asynchronous programming or to help us write more loosely coupled code.

If you need to learn more about design patterns, I suggest you look elsewhere.

conclusion

The Song Dynasty Zen master Qinghara Xingsi proposed three realms of zen:

At the beginning of meditation, see the mountain is the mountain, see the water is water; In meditation, the mountain is not the mountain, the water is not the water; In Zen, see the mountain is still a mountain, see the water is still water.

Similarly, programming also has a state: the first state of programming is to follow the flute, the second state is flexible use, and the third state is no pattern in mind. Only with more practice and more comprehension can we break through the realms of one after another.

In the end, may you finally be able to write code you don’t hate.

The last

It’s really the end of the line. I’ll add the above sample code when I have time. Welcome to Star & PR: All you need to know about code cleanliness

reference

  • How to Clean Code
  • The Art of Writing Readable Code
  • Refactoring – Improving the design of existing Code
  • JavaScript Design Patterns and Development Practices
  • Everyone Knows Design Patterns: Understanding Design Patterns from Life: Implementation in Python