More on this book
Community
Kindle Notes & Highlights
Read between
January 29 - April 30, 2019
Such hybrids make it hard to add new functions but also make it hard to add new data structures. They are the worst of both worlds. Avoid creating them.
If ctxt is an object, we should be telling it to do something; we should not be asking it about its internals.
The quintessential form of a data structure is a class with public variables and no functions. This is sometimes called a data transfer object, or DTO.
Active Records are special forms of DTOs. They are data structures with public (or bean-accessed) variables; but they typically have navigational methods like save and find. Typically these Active Records are direct translations from database tables, or other data sources.
treat the Active Record as a data structure and to create separate objects that contain the business rules and that hide their internal data
Objects expose behavior and hide data.
Data structures expose data and have no significant behavior.
Error handling is important, but if it obscures logic, it’s wrong.
The code is better because two concerns that were tangled, the algorithm for device shutdown and error handling, are now separated. You can look at each of those concerns and understand them independently.
In a way, try blocks are like transactions. Your catch has to leave your program in a consistent state, no matter what happens in the try.
Try to write tests that force exceptions, and then add behavior to your handler to satisfy your tests. This will cause you to build the transaction scope of the try block first and will help you maintain the transaction nature of that scope.
Create informative error messages and pass them along with your exceptions. Mention the operation that failed and the type of failure.
However, when we define exception classes in an application, our most important concern should be how they are caught.
In fact, wrapping third-party APIs is a best practice. When you wrap a third-party API, you minimize your dependencies upon it: You can choose to move to a different library in the future without much penalty.
You can define an API that you feel comfortable with.
Use different classes only if there are times when you want to catch one exception and allow the other one to pass through.
You create a class or configure an object so that it handles a special case for you. When you do, the client code doesn’t have to deal with exceptional behavior.
When we return null, we are essentially creating work for ourselves and foisting problems upon our callers.
Unless you are working with an API which expects you to pass null, you should avoid passing null in your code whenever possible.
In most programming languages there is no good way to deal with a null that is passed by a caller accidentally.
We are not suggesting that every use of Map be encapsulated in this form. Rather, we are advising you not to pass Maps (or any other interface at a boundary) around your system.
It’s not our job to test the third-party code, but it may be in our best interest to write tests for the third-party code we use.
Instead of experimenting and trying out the new stuff in our production code, we could write some tests to explore our understanding of the third-party code. Jim Newkirk calls such tests learning tests.1
Not only are learning tests free, they have a positive return on investment. When there are new releases of the third-party package, we run the learning tests to see whether there are behavioral differences.
First Law You may not write production code until you have written a failing unit test. Second Law You may not write more of a unit test than is sufficient to fail, and not compiling is failing. Third Law You may not write more production code than is sufficient to pass the currently failing test.
What this team did not realize was that having dirty tests is equivalent to, if not worse than, having no tests.
The moral of the story is simple: Test code is just as important as production code. It is not a second-class citizen. It requires thought, design, and care. It must be kept as clean as production code.
What makes a clean test? Three things. Readability, readability, and readability. Readability is perhaps even more important in unit tests than it is in production code.
Rather than using the APIs that programmers use to manipulate the system, we build up a set of functions and utilities that make use of those APIs and that make the tests more convenient to write and easier to read. These functions and utilities become a specialized API used by the tests. They are a testing language that programmers use to help themselves to write their tests and to help those who must read those tests later on.
That is the nature of the dual standard. There are things that you might never do in a production environment that are perfectly fine in a test environment. Usually they involve issues of memory or CPU efficiency. But they never involve issues of cleanliness.
I think the best thing we can say is that the number of asserts in a test ought to be minimized.
Perhaps a better rule is that we want to test a single concept in each test function.
With functions we measured size by counting physical lines. With classes we use a different measure. We count responsibilities.1
The name of a class should describe what responsibilities it fulfills. In fact, naming is probably the first way of helping determine class size.
We should also be able to write a brief description of the class in about 25 words, without using the words “if,” “and,” “or,” or “but.”
However, a system with many small classes has no more moving parts than a system with a few large classes. There is just as much to learn in the system with a few large classes. So the question is: Do you want your tools organized into toolboxes with many small drawers each containing well-defined and well-labeled components? Or do you want a few drawers that you just toss everything into?
Literate Programming.3
Private method behavior that applies only to a small subset of a class can be a useful heuristic for spotting potential areas for improvement.
It supports the SRP. It also supports another key OO class design principle known as the Open-Closed Principle, or OCP:4 Classes should be open for extension but closed for modification.
If a system is decoupled enough to be tested in this way, it will also be more flexible and promote more reuse.
It is a myth that we can get systems “right the first time.” Instead, we should implement only today’s stories, then refactor and expand the system to implement new stories tomorrow.
At all levels of abstraction, the intent should be clear.
Whether you are designing systems or individual modules, never forget to use the simplest thing that can possibly work.
A Timeless Way of Building,
According to Kent, a design is “simple” if it follows these rules: • Runs all the tests • Contains no duplication • Expresses the intent of the programmer • Minimizes the number of classes and methods The rules are given in order of importance.
To write clean code, you must first write dirty code and then clean it.