Clean Code Book Summary II
Yusuf Ali Koyuncu
Posted on January 25, 2024
Hello, in this article I am here with the last part of this series. You can reach the previous part of the series from the link below. So let’s start;
Clean Code Book Summary I
Yusuf Ali Koyuncu ・ Jan 25
#programming
#softwaredevelopment
#softwareengineering
#cleancode
Chapter-09 Unit Tests
- 3 rules of Test-Driven Development, these three rules keep you in the loop and iterate until the desired development is done;
- You cannot write production code without writing a failed unit test.
- You may not be able to write more unit tests than are enough to fail, and the compiling will fail.
- You cannot write more production code than is sufficient to pass the already failing test.
- Keep tests clean.
- Test code is just as important as production code.
- Code Base also makes it easier for us to make changes.
- Tests must be strictly dignified and readable.
- Must have one assert per test.
- Each test must test a single job.
- Clean tests follow this abbreviation. F.I.R.S.T.
- Fast → Tests should be fast.
- Independent → Tests must be independent.
- Repeatable → Tests must be testable in any environment.
- Self-Validating → Tests must contain a result.(Fail or Pass)
- Timely → Writing tests on time makes test writing more successful.
Chapter-10 Classes
- Class organization (according to java convention)
- Variables list.
- Public static constants variables.
- Private static variables.
- Private instance variables.
- Public variable.
- Public functions.
- Private utilities.
- Follows the above steps and helps the program read like a newspaper article.
- Encapsulation; Instead of accessing the variables in the class directly, it is to be accessed through functions.
- Classes must be small. To be able to measure, responsibility must be taken into account.
- If you use the words "if", "and", "or" and "but" when describing a class or function to someone, it means you have more than one responsibility. It is not clean.
- The Single Responsibility Principle (SRP); A class or module specifies only one responsibility, and it should have only one reason to change.
- The logic of doing something is done is wrong. You have to be focused on turning around and improving.
- You should have multiple structures that do the target work and rotate between themselves.
- Cohesion; You have to keep the structures that are close to each other together.
- Writing clean code greatly reduces the risk at the time of change.
- In some developments, that is, writing a class preserves the SRP feature of the system. This is what needs to be done. In this way, it allows us to add new structures to be added without changing the existing structure.
- Abstract classes for isolation, depend on interfaces.
Chapter-11 Systems
- Complexity should be measured.
- The construction phase and the usage phase are different from each other and should be separated.
- Builder patterns can be used.
- Dependency Injection; It allocates secondary responsibilities from one object (IOC) to other dedicated objects, thus supporting SRP.
- Dependency injection provides method and constructor arguments that inject dependencies and link dependencies together.
- System Design is an important element.
- Separate concerns adequate structure should be properly constructed.
- Aspect-oriented programming (AOP) should be used to solve cross-cutting concerns.
- Focus on systems that are simple and open to expansion.
- The system should be designed as testable.
- If you can see the standards add value, you should use them.
- System must be clean. Processes should be advanced and a TDD-oriented approach should be exhibited.
- No matter what level you are, you should think simply.
Chapter-12 Emergence
- We find Kent Beck's four rules to be of great help in creating well-designed software. According to Kent, a design is simple if it follows these rules;
- Runs all tests.
- Does not contain duplicate code.
- If the programmer's purpose is clearly stated.
- Minimizes the number of classes and methods.
- Refactoring is important. It should be done regularly and questioned.
- Pay attention to the naming pattern when using the design pattern. This improves readability.
- It takes a lot of practice.
Chapter-13 Concurrency
- Objects are absorptions of processes. Threads belong to the schedule.
- Concurrent programming is not easy. Especially when it starts working under load, problems will arise.
- Concurrency is a decoupling strategy. (Decoupling)
- The schedule determines when it will work.
- Dealing with more than one thing at the same time.
- Concurrency can always improve performance.
- If a concurrency system is to be built, the design must be changed.
- You need to know how to deal with concurrency updates and deadlock.
- It is difficult to simulate the same error again.
- Concurrency has a life cycle. But it should be kept completely different and separate from other code parts.
- "Lock" is used for data consistency. Atomic structures perform the same process on the back.
- Keep data encapsulation ahead and reduce tampering with shared data.
- Threads must be independent. Examples, ReentrantLock, and Semaphore CountDownLatch. (Suggestion: Check out the classes you can use.)
- Execution Models; Bound Resources, Mutual Exclusion, Starvation, Deadlock, Livelock. Examples; Producer-Consumer, Readers-Writers, Dining Philosophers. (Suggestion: Learn these basic algorithms and understand their solutions)
- Synchronized Methods; Client-Based Locking, Server-Based Locking, Adapted Server. (Suggestion: Avoid using multiple methods on a shared object)
- Keep your synchronized partitions as small as possible.
- It is Difficult to Write the Correct Shut-Down Code. The graceful shutdown can be difficult to fix. (Suggestion: Think about shutdown early and getting it working early. It will take longer than expected. Review existing algorithms because this is probably harder than you think.)
- Experiment with different scenarios to test.
- Handle threading errors.
- Run nonthreaded codes first.
- Make thread code pluggable.
- Make thread code adjustable.
- Invoke more threads than the number of processors.
- Run on different platforms.
- Use code to try errors and force the system.
Chapter-14 Successive Refinement
- This chapter is a case study examining the argument parser structure of a CLI application.
- It is explained through the code. This chapter should be read directly.
- Working code is just useless. It will break in the future and will not work.
- The impact of bad code on a project is huge.
- Bad code creates a lot of technical debt.
- It costs a lot of time to clean the code.
- Bad code creates multiple dependencies and makes it more difficult to resolve.
Chapter-15 JUnit Internals
- This chapter critiques some of the JUnit framework.
- It is explained through the code. This chapter should be read directly.
- When it comes to frameworks, JUnit is simple in design, precise in definition, and clever in implementation.
- Coverage of tests is important.
- Each of us has a responsibility to leave the code a little better than we found it.
Chapter-16 Refactoring SerialDate
- In this chapter, the org.jfree.date package of the JCommon library has the SerialDate class and refactors it.
- It is explained through the code. This chapter should be read directly.
- Criticism is good and it develops people.
- First make the code work. Make sure you test everything and make sure it passes the tests.
- Leave the code cleaner than you found it. This may take time, but it’s worth it.
Chapter-17 Smells and Heuristics
This chapter talks about the definition of “Code Smells” in Martin Fowler’s Refactoring book.
Comments
- Inappropriate Information; keep everything but the code in a different place. Comments should be reserved for technical notes on code and design.
- Obsolete Comment; Reviews age fast. You should delete the comment at the point where you can.
- Redundant Comment; unnecessary comments.
- Poorly Writer Comment; There should be no incomplete or poorly written comments.
- Commented-Out Code; if a code is being commented out, it’s an abomination.
Environment
- Build Requires More Than One Step; Running a project should be simple.
- Tests Require More Than One Step; Running tests should be easy.
Functions
- Too Many Arguments; functions must have few arguments. Question yourself if he has more than 3–4 arguments.
- Output Arguments; If the function needs to change the state of an object, have it change the state of the object it is called from, rather than passing it as output.
- Flag Arguments; Having a boolean parameter means that it is overworking.
- Dead Function; delete dead functions.
General
- Multiple Languages in One Source File; avoid using multiple languages within a file.
- Obvious Behavior Is Unimplemented; The code we read should meet our expectations. It shouldn’t offer anything different while waiting for an output.
- Incorrect Behavior at the Boundaries; don’t trust the code you wrote, try to write a test for each boundary condition.
- Overridden Safeties; skip security steps.
- Duplication; It is one of the most important rules. Dave Thomas and Andy Hunt called it the DRY principle (Don’t Repeat Yourself). Kent Beck has made this one of the core tenets of Extreme Programming, “Once, and only once.” said. Ron Jeffries ranks this rule just below making sure all tests pass. Any time you see duplication in code, it represents a missed opportunity for abstraction. Replace switch/case or if/else structures with polymorphism. Modules that have similar algorithms but don’t share similar lines of code are even more subtle. This is again a repetition and should be handled using “TEMPLATE METHOD” or “STRATEGY PATTERN”.
- Code at Wrong Level of Abstraction; There should be no abstraction at the wrong level.
- Base Classes Depending on Their Derivatives; The base class should not depend on what comes from the subclass.
- Too Much Information; well-defined modules have very small interfaces that let you do a lot with very little. This way interfaces should be defined.
- DeadCode; dead ie unused codes should be detected and deleted.
- Vertical Separation; variables and function should be defined close to where they are used.
- Inconsistency; names should be named consistently according to the work they do on the whole project.
- Clutter; You should delete what you don’t use.
- Artificial Coupling; Things that are not connected should not be artificially combined.
- Feature Envy; The methods of one class should deal with the variables and functions of the class they belong to, not the variables and functions of the other class.
- Selector Arguments; It’s better to have many functions than to pass some code to one function to choose the behavior.
- Obscured Intent; The code must be meaningful and readable.
- Misplaced Responsibility; Make sure you put the code you are using in the right place. You have to keep related structures together.
- Inappropriate Static; You should prefer non-static methods to static methods. If you really want a function to be static, make sure you don’t have the chance to want it to behave polymorphically.
- Use Explanatory Variables; variable names should be good and descriptive.
- Function Names Should Say What They Do; A function name should describe what it does.
- Understand the Algorithm; The best way to understand the algorithm is to refactor the function until you have something clean and meaningful enough to clearly show how it works.
- Make Logical Dependencies Physical; if one module depends on another, that dependency must not only be logical but also physical.
- Prefer Polymorphism to If/Else or Switch/Case; When writing code, replace the switch/case or if/else structure with polymorphism.
- Follow Standard Conventions; Team rules and standards need to be determined.
- Replace Magic Numbers with Named Constants; having raw numbers in your code is a bad idea. You should hide them behind well-named constants.
- Be Precise; The reason for ambiguities and inconsistencies in the code is either the result of disagreements or laziness. In both cases, they must be eliminated. That’s why you have to be determined.
- Structure Over Convention; we must keep the structural rules above all else.
- Encapsulate Conditionals; If more than one structure is checked in the if condition, it should be extracted in a method and the method should be called in the if condition.
- Avoid Negative Conditionals; Positive conditions should be checked in the if condition.
- Functions Should Do One Thing; the function should be responsible for and do a single job.
- Hidden Temporal Couplings; use arguments that make temporal coupling explicit.
- Don’t Be Arbitrary; Your code structure should be correct and consistent.
- Encapsulate Boundary Conditions; +1 or -1 may be needed in some controls. Don’t let them leak all over the code. We don’t want the +1’s and -1’s to scatter left and right.
- Functions Should Descend Only One Level of Abstraction; The expressions within the function must all be written at the same abstraction level, which must be one level below the operation described by the function name.
- Keep Configurable Data at High Levels; high-level constants are easy to change.
- Avoid Transition Navigation; A to B; If B and C cooperate, we don’t want modules A to know about C. So A; He must not know C over B.
Java
- Avoid Long Import Lists by Using Wildcards; If you are using two or more classes from a package, import the entire package.
- Don’t Inherit Constants; constants are hidden at the top of the inheritance hierarchy. Don’t use inheritance as a way to cheat the language’s scope determination rules.
- Constants versus Enums; Refer to enum instead of defining public static final int.
Names
- Choose Descriptive Names; Choose descriptive and meaningful names.
- Choose Names at the Appropriate Level of Abstraction; Choose correct naming according to abstraction level.
- Use Standard Nomenclature Where Possible; use nouns denoting the work done in all nomenclature.
- Unambiguous Names; Do not be afraid of long names, the important thing is that it is descriptive.
- Use Long Names for Long Scopes; The nomenclature should be chosen according to its scope.
- Avoid Encodings; Do not use encrypted names.
- Names Should Describe Side-Effects; naming should describe everything that a function, variable, or class is or does. Do not store Side-Effects with a naming, specify them in the naming.
Tests
- Insufficient; test code should test anything that might break in the code. Tests are inadequate as long as there are unverified tests or conditions undiscovered by calculations.
- Use a Coverage Tool; Use tools to see what tests cover in the code.
- Don’t Skip Trivial Tests; Don’t be afraid to write tests, even if the cases are trivial.
- An Ignored Test is a Question about an Ambiguity; If you’re ignoring your test, query your code.
- Test Boundary Conditions; Take particular care to test boundary conditions. Often we get the middle of an algorithm right, but we get the bounds wrong.
- Exhaustively Tested Near Bugs; When you find a bug in a function, it’s wise to do a thorough testing of that function.
- Patterns of Failure Are Revealing; poorly organized test cases will reveal failure patterns.
- Test Coverage Patterns Can Be Revealing; Looking at code that may or may not be executed by passed tests gives clues as to why failed tests.
- Tests Should Be Fast; a slow test is a test that will not run. Do what you have to do to keep your tests fast.
“Bad programmers worry about the code. Good programmers worry about data structures and their relationships.” — Linus Torvalds
Reference
💖 💪 🙅 🚩
Yusuf Ali Koyuncu
Posted on January 25, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.