# Mjukvarukvalité ## Kapitel 6 - Objects and Data Structures Hiding implementation is not just a matter of putting a layer of functions between the variables. Hiding implementation is about abstractions! A class does not simply push its variables out through getters and setters. Rather it exposes abstract interfaces that allow its users to manipulate the essence of the data, without having to know its implementation. **We do not want to expose the details of our data.** Rather we want to express our data in abstract terms. This is not merely accomplished by using interfaces and/or getters and setters. public interface Vehicle { double getFuelTankCapacityInGallons(); double getGallonsOfGasoline(); } public interface Vehicle { double getPercentFuelRemaining(); } **These two examples show the difference between objects and data structures. Objects hide their data behind abstractions and expose functions that operate on that data. Data struc- ture expose their data and have no meaningful functions.** ![](https://i.imgur.com/ZZWZMjh.png) ![](https://i.imgur.com/QPv3SG1.png) Procedural code (code using data structures) makes it easy to add new functions without changing the existing data structures. OO code, on the other hand, makes it easy to add new classes without changing existing functions. Procedural code makes it hard to add new data structures because all the functions must change. OO code makes it hard to add new functions because all the classes must change. ### The law of demeter Objects hide their data and expose operations. The method should not invoke methods on objects that are returned by any of the allowed functions. In other words, talk to friends, not to strangers. #### Train Wrecks ex: final String outputDir = ctxt.getOptions().getScratchDir().getAbsolutePath(); This kind of code is often called a train wreck because it look like a bunch of coupled train cars. Chains of calls like this are generally considered to be sloppy style and should be avoided [G36]. It is usually best to split them up as follows: Options opts = ctxt.getOptions(); File scratchDir = opts.getScratchDir(); final String outputDir = scratchDir.getAbsolutePath(); ![](https://i.imgur.com/OV3VTgZ.png) #### Data Transfer Objects The quintessential form of a data structure is a class with public variables and no func- tions. This is sometimes called a data transfer object, or DTO. DTOs are very useful struc- tures, especially when communicating with databases or parsing messages from sockets, and so on. They often become the first in a series of translation stages that convert raw data in a database into objects in the application code. Somewhat more common is the “bean” form shown in Listing 6-7. Beans have private variables manipulated by getters and setters. The quasi-encapsulation of beans seems to make some OO purists feel better but usually provides no other benefit. ![](https://i.imgur.com/8s935D5.png) ### Conclusion KAPTITEL 6 **Objects expose behavior and hide data. This makes it easy to add new kinds of objects without changing existing behaviors. It also makes it hard to add new behaviors to existing objects.** **Data structures expose data and have no significant behavior. This makes it easy to add new behaviors to existing data structures but makes it hard to add new data structures to existing functions.** ## Kapitel 7 - Error handling without exceptions The caller must check for errors immediately after the call. Unfortunately, it’s easy to forget. For this rea- son it is better to throw an exception when you encounter an error. The calling code is cleaner. Its logic is not obscured by error handling. The code is better because two concerns that were tangled, the algorithm for device shutdown and error han- dling, are now separated. You can look at each of those concerns and understand them independently. When you execute code in the try portion of a try-catch-finally statement, you are stating that execution can abort at any point and then resume at the catch. Try to write tests that force exceptions, and then add behavior to your handler to sat- isfy 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. Do not use checked exceptions: Consider the calling hierarchy of a large system. Functions at the top call functions below them, which call more functions below them, ad infinitum. Now let’s say one of the lowest level functions is modified in such a way that it must throw an exception. If that exception is checked, then the function signature must add a throws clause. But this means that every function that calls our modified function must also be modified either to catch the new exception or to append the appropriate throws clause to its signature. Ad infinitum. The net result is a cascade of changes that work their way from the lowest levels of the software to the highest! Encapsulation is broken because all functions in the path of a throw must know about details of that low-level exception. Given that the purpose of exceptions is to allow you to handle errors at a distance, it is a shame that checked excep- tions break encapsulation in this way. #### Provide context With stack trace and also create informative error messages and pass them along with your exceptions. #### Classify errors There are many ways to classify errors. We can classify them by their source: Did they come from one component or another? Or their type: Are they device failures, network failures, or programming errors? However, when we define exception classes in an appli- cation, our most important concern should be how they are caught. ![](https://i.imgur.com/IF1lo2g.png) #### Wrappers Wrappers like the one we defined for ACMEPort can be very useful. 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. Wrapping also makes it easier to mock out third-party calls when you are testing your own code. One final advantage of wrapping is that you aren’t tied to a particular vendor’s API design choices. You can define an API that you feel comfortable with. In the preceding example, we defined a single exception type for port device failure and found that we could write much cleaner code. #### Passing null Returning null from methods is bad, but passing null into methods is worse. Unless you are working with an API which expects you to pass null, you should avoid passing null in your code whenever possible. It’s good documentation, but it doesn’t solve the problem. If someone passes null, we’ll still have a runtime error. In most programming languages there is no good way to deal with a null that is passed by a caller accidentally. Because this is the case, the rational approach is to forbid passing null by default. When you do, you can code with the knowledge that a null in an argument list is an indication of a problem, and end up with far fewer careless mistakes. ### Conclusion - KAPITEL 7 Clean code is readable, but it must also be robust. These are not conflicting goals. We can write robust clean code if we see error handling as a separate concern, something that is viewable independently of our main logic. To the degree that we are able to do that, we can reason about it independently, and we can make great strides in the maintainability of our code. ## Kapitel 8 - Boundaries **There is a natural tension between the provider of an interface and the user of an interface.** Providers of third-party packages and frameworks strive for broad applicability so they can work in many environments and appeal to a wide audience. Users, on the other hand, want an interface that is focused on their particular needs. This tension can cause problems at the boundaries of our systems. Vi gömmer inbyggda funktioner och exponerar bara det som vi vill att användaren ska komma åt ![](https://i.imgur.com/zD1VmzT.png) #### Learning tests Learning the third-party code is hard. Integrating the third-party code is hard too. Doing both at the same time is doubly hard. What if we took a different approach? 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. In learning tests we call the third-party API, as we expect to use it in our application. We’re essentially doing controlled experiments that check our understanding of that API. The tests focus on what we want out of the API. #### Learning Tests Are Better Than Free The learning tests end up costing nothing. We had to learn the API anyway, and writing those tests was an easy and isolated way to get that knowledge. The learning tests were precise experiments that helped increase our understanding. 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. Learning tests verify that the third-party packages we are using work the way we expect them to. Once integrated, there are no guarantees that the third-party code will stay compatible with our needs. The original authors will have pressures to change their code to meet new needs of their own. They will fix bugs and add new capabilities. With each release comes new risk. If the third-party package changes in some way incompatible with our tests, we will find out right away. Whether you need the learning provided by the learning tests or not, a clean boundary should be supported by a set of outbound tests that exercise the interface the same way the production code does. Without these boundary tests to ease the migration, we might be tempted to stay with the old version longer than we should. ### Clean Boundaries Code at the boundaries needs clear separation and tests that define expectations. We should avoid letting too much of our code know about the third-party particulars. It’s better to depend on something you control than on something you don’t control, lest it end up controlling you. We manage third-party boundaries by having very few places in the code that refer to them. We may wrap them as we did with Map, or we may use an ADAPTER to convert from our perfect interface to the provided interface. Either way our code speaks to us better, promotes internally consistent usage across the boundary, and has fewer maintenance points when the third-party code changes. ## Kapitel 9 - Unit tests **The Three Laws of TDD** By now everyone knows that TDD asks us to write unit tests first, before we write production code. But that rule is just the tip of the iceberg. Consider the following three laws - 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. The tests and the production code are written together, with the tests just a few seconds ahead of the production code. What makes a clean test? Three things. Readability, readability, and readability. Read- ability is perhaps even more important in unit tests than it is in production code. What makes tests readable? The same thing that makes all code readable: clarity, simplicity, and density of expression. In a test you want to say a lot with as few expressions as possible. Testa API för sig och den beroende koden för sig med mockdata. - One Assert per Test - we want to test a single concept in each test function. ### F.I.R.S.T Clean tests follow five other rules that form the above acronym: **Fast** - Tests should be fast. They should run quickly. When tests run slow, you won’t want to run them frequently. If you don’t run them frequently, you won’t find problems early enough to fix them easily. You won’t feel as free to clean up the code. Eventually the code will begin to rot. **Independent** - Tests should not depend on each other. One test should not set up the condi- tions for the next test. You should be able to run each test independently and run the tests in any order you like. When tests depend on each other, then the first one to fail causes a cas- cade of downstream failures, making diagnosis difficult and hiding downstream defects. **Repeatable** - Tests should be repeatable in any environment. You should be able to run the tests in the production environment, in the QA environment, and on your laptop while riding home on the train without a network. If your tests aren’t repeatable in any environ- ment, then you’ll always have an excuse for why they fail. You’ll also find yourself unable to run the tests when the environment isn’t available. **Self-Validating** - The tests should have a boolean output. Either they pass or fail. You should not have to read through a log file to tell whether the tests pass. You should not have to manually compare two different text files to see whether the tests pass. If the tests aren’t self-validating, then failure can become subjective and running the tests can require a long manual evaluation. **Timely** - The tests need to be written in a timely fashion. Unit tests should be written just before the production code that makes them pass. If you write tests after the production code, then you may find the production code to be hard to test. You may decide that some production code is too hard to test. You may not design the production code to be testable. ### Conclusion - Kapitel 9 Tests are as important to the health of a project as the production code is. Perhaps they are even more important, because tests preserve and enhance the flexibility, maintainability, and reusability of the production code. So keep your tests con- stantly clean. Work to make them expressive and succinct. Invent testing APIs that act as domain-specific language that helps you write the tests. If you let the tests rot, then your code will rot too. Keep your tests clean. ## Kapitel 10 - Classes Following the standard Java convention, a class should begin with a list of variables. Pub- lic static constants, if any, should come first. Then private static variables, followed by pri- vate instance variables. There is seldom a good reason to have a public variable. **Encapsulation** We like to keep our variables and utility functions private, but we’re not fanatic about it. Sometimes we need to make a variable or utility function protected so that it can be accessed by a test. For us, tests rule. If a test in the same package needs to call a function or access a variable, we’ll make it protected or package scope. However, we’ll first look for a way to maintain privacy. Loosening encapsulation is always a last resort. **Classes Should Be Small!** With functions we measured size by counting physical lines. With classes we use a different measure. We count responsibilities. The name of a class should describe what responsibilities it fulfills. In fact, naming is probably the first way of helping determine class size. If we cannot derive a concise name for a class, then it’s likely too large. 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. ### The Single Responsibility Principle ... ...states that a class or module should have one, and only one, reason to change. This principle gives us both a definition of responsibility, and a guidelines for class size. Classes should have one responsibility—one reason to change. ### Cohesion Classes should have a small number of instance variables. Each of the methods of a class should manipulate one or more of those variables. In general the more variables a method manipulates the more cohesive that method is to its class A class in which each variable is used by each method is **maximally cohesive.** we would like cohesion to be high. When cohesion is high, it means that the methods and variables of the class are co-dependent and hang together as a logical whole. **When classes lose cohesion, split them!** ## Kapitel 11 - Systems Software systems should separate the startup process, when the application objects are constructed and the dependencies are “wired” together, from the runtime logic that takes over after startup. The startup process is a concern that any application must address. It is the first con- cern that we will examine in this chapter. The separation of concerns is one of the oldest and most important design techniques in our craft. **Factories** Sometimes, of course, we need to make the application responsible for when an object gets created. For example, in an order processing system the application must create the LineItem instances to add to an Order. In this case we can use the ABSTRACT FACTORY pattern to give the application control of when to build the LineItems, but keep the details of that construction separate from the application code. **Dependency injection** A powerful mechanism for separating construction from use is Dependency Injection (DI), the application of Inversion of Control (IoC) to dependency management.3 Inversion of Control moves secondary responsibilities from an object to other objects that are dedicated to the purpose, thereby supporting the Single Responsibility Principle. In the context of dependency management, an object should not take responsibility for instantiating depen- dencies itself. Instead, it should pass this responsibility to another “authoritative” mecha- nism, thereby inverting the control. Because setup is a global concern, this authoritative mechanism will usually be either the “main” routine or a special-purpose container. **Scaling Up** Cities grow from towns, which grow from settlements. At first the roads are narrow and practically nonexistent, then they are paved, then widened over time. Small buildings and empty plots are filled with larger buildings, some of which will eventually be replaced with skyscrapers. It is a myth that we can get systems “right the first time.” Instead, we should imple- ment only today’s stories, then refactor and expand the system to implement new stories tomorrow. This is the essence of iterative and incremental agility. Test-driven develop- ment, refactoring, and the clean code they produce make this work at the code level. **Cross-Cutting Concerns** ### Conclusion - Kapitel 11 Systems must be clean too. An invasive architecture overwhelms the domain logic and impacts agility. When the domain logic is obscured, quality suffers because bugs find it easier to hide and stories become harder to implement. If agility is compromised, productivity suffers and the benefits of TDD are lost. At all levels of abstraction, the intent should be clear. This will only happen if you write POJOs (Plain-Old Java Object) and you use aspect-like mechanisms to incorporate other implementation concerns noninvasively. Whether you are designing systems or individual modules, never forget to use the simplest thing that can possibly work.