Object Oriented Programming

Practical Object-Oriented Design in Ruby, by Sandi Metz, is a great read.

This is a book for programmers. Whatever the language, whatever the skill, if you’re an engineer working on an object-oriented application, this will help.

I have a deep respect for anyone that can distill complicated and controversial issues. Out of the many “This is Best Way” type of engineering books out there, this one leans objective, which is great. The one part of engineering I don’t like is the bickering. Nothing is actually controversial.

Get shit working. Do it well.

If you’re too lazy to read the book, here are some spark notes.

Chapter 1 – OO Design

  1. An OO application only consists of two core concepts: various parts and how the parts interact with each other. A part is an object and how they interact with each other is by sending messages.
  2. Software design is defined as how you arrange code. Many programmers, many designs, some good, some bad — same implementation.
  3. Managing dependencies means arranging your code so that objects can tolerate change. Objects that know too much about each other cause small changes to become destructive.
  4. The purpose of good software design is to enable more good design and reduce the overall cost of change.
  5. Patterns are good. Solve common problems in common ways and do not abuse.
  6. Over-designs are bad. Big Up Front Designs — BUFD — do not work in any kind of Agile setting. This is not to say we do not need design. The whole point of Agile is that it guarantees things are going to change.
  7. Overall goal of software is to keep the average cost per feature as low as possible. Software design itself takes time, and thus is a cost.
  8. Too much design up front without a line of code written for half a year is bad. Not designing well and making future software miss deadlines is bad.
  9. We should write code that saves time and money that same day.
  10. An application can be deemed successful if it lives a long time. Applications survive by being able to deal with change. Good software design enables this change.
  11. For a metric of bad OO design, just see how difficult it is to change something.

Chapter 2 – Single-Purpose Classes

  1. Technical knowledge and organization are two sides of a coin. You need to know how to write code, and where to put it.
  2. “Easy to Change” is defined as changes that have no unexpected side effects. A small requirement should mean a small change in code.
  3. A class should do the smallest possible useful thing and be explained in one sentence. If you use the word AND, then the class has more than one responsibility. If you use the word OR, then the class has multi-responsibilities which aren’t even related.
  4. Multi-responsibility classes are difficult to reuse. Cherrypicking explicit parts you need from outside the class implies that things are probably entangled inside the class.
  5. You only know the the feature requests of today, not the future. Pre-optimization is bad. Over-design is bad.
  6. When the future cost of doing nothing is the same as the current cost, postpone any decision making. For a small, single developer, application, 99.9% of the time the move is “postpone decision”. For a multi-developer application, this is a different story. E.g. You might have to make a preemptive design decision to cut a larger future cost that affects your coworkers.
  7. Depend on behavior, not data. Encapsulate and send messages to access variables.
  8. Methods — like classes — should have a single responsibility.
  9. Single responsibility exposes hidden qualities, removes the need for comments, and encourages reuse.
  10. Single responsibility entities are easy to move around, easy to isolate, and easy to design with.

Chapter 3 – Managing Dependencies

  1. Decouple classes and make sure classes “know” as little as possible.
  2. Use dependency injection, this naturally leads to more loosely coupled classes.
  3. If you’re unable to remove a dependency, try to isolate it. Reveal dependencies and lower the barrier to refactor.
  4. Choose dependency direction wisely. Depend on things that change less often. Within your code this is up to your own discretion. Also assess the stability of frameworks when using them.
  5. The most hazardous classes are the ones who have a lot dependencies and are likely to have their requirements change often.

Chapter 4 – Flexible Interfaces

  1. When approaching OO design, your mindset should be on the messages more than the classes themselves. OO applications in their nature are defined by the messages that flow between objects.
  2. Take care in defining public interfaces. These are the stable parts.
  3. Always keep the intention in mind when designing. Use diagrams to help.
  4. When designing messages, it’s better to ask for “What” instead of telling “How”. You don’t tell the receiver how the behave, you send a message based on what the sender wants.
  5. We should use keywords as best we can. Public. Protected. Private. Mileage varies across languages.
  6. Honor the public interfaces of other classes and trust them. Trust leads to more OO and less procedural code.
  7. Try to follow the Law of Demeter. “Only talk to your immediate neighbor”. Be wary of message chaining that becomes too long.
  8. Long message chains are influenced by objects that you know about as a programmer. You know the interface so you can construct message chains together to get to distant behavior.

Chapter 5 – Duck Typing

  1. If an object quacks like a duck and walks like a duck — then it’s a duck.
  2. An object’s class is just one way for it to acquire its public interface. Across-class types — duck types — allow for flexible public interfaces. Be explicit about these and document them well.
  3. Duck typing inherently make things more abstract. Concrete code is easy to understand, but hard to extend. Abstract code is more obscure, but once understood it’s easier to change.
  4. As a software developer, we must have a tolerance to a certain level of abstraction and ambiguity in the code.
  5. A case statement that switches on a class or domain object is probably covering a hidden duck type. It automatically shows various entities that have something in common.
  6. There’s always pros and cons of static v. dynamic typed languages. It’s a matter of preference.
  7. Meta programming — with great power comes great responsibility.

Chapter 6 – Behavior Through Inheritance

  1. Subclasses should be everything their parent class is, plus more. Always remember the “Is-A” relationship.
  2. It may be helpful to start with a blank abstract class which exists to be subclassed. These provide a repository for shared behavior. This behavior can be selectively promoted to the superclass if we see it repeated many times across subclasses. Promote consciously.
  3. It’s pre-optimization when we have an abstract superclass with only one subclass.
  4. When using template method pattern, we must always provide a default implementation even if it’s just to raise an exception. It makes writing a subclass more clear when it’s to be implemented.
  5. Use template method pattern to invite subclasses to provide their specializations. When working with many developers, this can help your peers.
  6. Subclasses can be decoupled and less error-prone if we use hook messages. E.g. a post_initialize() hook inside an initializer instead of having a subclass issue a call to super. Try to reduce coupling as much as possible.
  7. Well-designed inheritance hierarchies should be easy to extend with new subclasses. This can be helpful to programmers who are new to the code base.

Chapter 7 – Share Roles Through Modules

  1. Common behavior orthogonal to a class can be called a “role”. These take the form of modules in Ruby, but also takes many shapes in other languages.
  2. Objects can respond to many messages: Those it implements, those implemented by objects above it in the hierarchy, those implemented by modules/mixins added to the class, and those implemented by modules/mixins added to objects above it in the hierarchy.
  3. Objects should manage themselves and should only contain only their own behavior. If you’re interested in object B, you should not be forced to know about object A to find things about object B.
  4. A role should follow the “Behaves-Like-A” pattern. This is significantly different than the “Is-A” pattern.
  5. When a sending object checks the class of a receiving object to determine a message, you have overlooked a duck type and/or role.
  6. An object that has uses an attribute like category or type to determine messages to send to itself is worrisome.
  7. All code in an abstract superclass should apply to every class that inherits to it.
  8. Template method pattern should also be used in modules to invite classes that include them to supply specializations. Avoid code that requires inheritors to send super, use hooks when possible. This applies to both class hierarchies and roles.
  9. Try to create shallow hierarchies. This applies to both class hierarchies and roles.

Chapter 8 – Composition

  1. The core of a class-based relationship is that we get automatic message delegation that invokes the correct behavior. With the hierarchy, we get some level of message delegation for free.
  2. With composition, the objects stand alone since messages must be explicitly delegated. However, composition gives us more structural independence than inheritance.
  3. If you can’t explicitly defend inheritance as a better solution, use composition. Composition inherently contains fewer built-in dependencies. Mileage varies by language.
  4. Inheritance hurts more than composition when used incorrectly.
  5. Inheritance hurts more when working with many developers. If you write code for purposes that others do not anticipate in an import superclass, other programmers might not be able to tolerate such changes and the demands of inheritance. The coupling is very tight.
  6. Avoid writing frameworks that require users of your code to subclass your objects to gain behavior. You do not know what their application code looks like and they might not be able to easily inherit from objects in your framework.
  7. Composed objects relies on its parts. Even though the smaller parts may be transparent, the whole may not be so transparent. Composition provides structural independence at the cost of a decrease in overall transparency.
  8. Composed objects must explicitly know which messages to delegate and to whom.
  9. Inheritance — “Is A”
  10. Duck Typing — “Behaves Like A”
  11. Composition — “Has A”
  12. All three of these techniques are available for us to use to design our code. Each has their own costs and benefits.

 

 

 

Discussions — No responses yet