Having been a Ruby programmer full-time for a year now, this book finally made "click" many of the Best Practices I've seen and used in code but haven't really been able to articulate. It got a little long-winded and redundant at some points, probably because it's geared more towards people who haven't been exposed much to OO, but overall it was definitely worth reading.
These are some of the parts that were most valuable to me:
"Sometimes the value of having the feature right now is so great that it outweighs any future increase in costs."
"Design is more the art of preserving changeability that it is the act of achieving perfection."
On designing a class: "If the simplest description you can devise uses the word 'and,' the class likely has more than one responsibility. If it uses the word 'or,' then the class has more than one responsibility and they aren't even very related."
"When faced with an imperfect and muddled class ... ask yourself: 'What is the future cost of doing nothing today?'"
"If you can control the input, pass in a useful object, but if you are compelled to take a messy structure, hide the mess even from yourself."
On classes with too many dependencies: "Because they increase the chance that [the class] will be forced to change, these dependencies turn minor code tweaks into major undertakings where small changes cascade through the application, forcing many changes."
"depend on things that change less often than you do."
"Classes control what's in your source code repository; messages reflect the living, animated application."
"public methods should read like a description of responsibilities."
"Interfaces evolve and to do so they must first be born. It is important that a well-defined interface exist than it be perfect."
"Even if the original author did not define a [good] public interface it is not too late to create one for yourself."
"The general rule for refactoring into a new inheritance hierarchy is to arrange code so that you can promote abstractions rather than demote concretions."
"Identifying the correct abstraction is easiest if you have access to at least three existing concrete classes."
"Because `extend` adds the module's behavior directly to an object, extending a class with a module creates class methods in that class and extending an instance of a class with a module creates instance methods in that instance."
"Aggregation is exactly like composition except that the contained object has an independent life [outside of the has-a relationship]."
"If you cannot explicitly defend inheritance as a better solution, use composition.
"Inheritance is best suited to adding functionality to existing classes when you will use most of the old code and add relatively small amounts of new code."
"Tests provide the only reliable documentation of design. The story they tell remains true long after paper documents become obsolete and human memory fails. Write your tests as if you expect your future self to have amnesia."
"Costly tests do not necessarily mean that the application is poorly designed. It is quite technically possible to write bad tests for well-designed code... The best way to [design good tests] is to write loosely coupled tests about only the things that matter."
"It is an unfortunate truth that the most complex code is usually written by the least qualified person... Novice programmers don't yet have the skills to write simple code."
"Do not test an incoming message that has no dependents; delete it; Your application is improved by ruthlessly eliminating code that is not actively being used. Such code is negative cash flow, it adds testing and maintenance burdens but provides no value. Deleting unused code saves money right now, if you do not do so you must test it."
If using an object is cheap, injecting an actual one instead of a double into a test is fine.
"An object with many private methods exudes the design smell of having too many responsibilities. If your object has so many private methods that you dare not leave them untested, consider extracting the methods into a new object."
"When you treat test doubles as you would any other role player and test them to prove their correctness, you avoid test brittleness and can stub without fear of consequence."
"On testing abstract base classes: "If you leverage Liskov and create new subclasses that are used exclusively for testing, consider requiring these subclasses to pass your subclass responsibility test to ensure they don't accidentally become obsolete."