DRY! KISS! YAGNI!
That's good, and I'm always approving someone that takes the first steps of a personal kaizen and starts improving himself. However, I already tried to go againsta the tide with the How to be a worse programmer article.
Today I'll talk instead of another acronym I never see in this type of posts: GRASP, which stands for General Responsibility Software Patterns (or Principles if you prefer). GRASP is a collection of very high-level patterns (almost only ideas, concepts to keep in mind) for clean object-oriented programming.
As such there will be no specific code here, but some principles to adhere. However, I'll include an example of application in each principle definition, since I do not like abstract-level talking about software. You'll see classes, objects and methods cited in every phrase.
Objects that exist only for the purpose of creating shorter-lived objects are acceptable, and encouraged: the creation of objects can be one of the most powerful but complex aspects in an application.
As an example, the original (Gang of Four) Design Patterns book includes creational patterns as one of the three categories of patterns. I personally cannot live without the [Abstract] Factory pattern, but I find myself sometimes implementing the undervalued Builder and Prototype.
Place responsibility in the class with the most information available to fulfill it, to promote information hiding and not forcing other classes to expose information only for the purpose of passing them to another object.
Unuseful getters are a classic mistake. If some one calls a getter, maybe the graph can be refactored to a Tell, don't Ask solution, where the client code tells the neighbor to do something, instead of asking for its data to perform an operation by himself. If no one calls a getter, delete it. It's simple. The same goes for setters.
Information hiding is a related principle: its purpose is trying to isolate the changes in single points or areas of the codebase, and avoid them rippling all over the project. When a class does not need to know the value or even the existence of an information, it shouldn't know, period.
There are objects responsible for dealing with user events and coordinating other objects. For example, they may handle the domain objects in a particular scenario.
Both when dealing with UI events or HTTP requests, the Controllers are a fundamental part of MVC machines. This pattern, in non-web and web form, has in controller a crucial steering point, that is often fill of alien responsibilities but that cannot be eliminated either.
A design should strive for keeping a low coupling of a class to other classes or packages. Reuse is favored by low coupling, since you can detach a component from its collaborating code and transport it in another project. Testability is also influenced, since the test environmente achieves a better isolation of the system under test, a single object or component.
By definition, when a change is introduced (and it will) in a system with low coupled components, it will tend to remain local instead of spreading its effects to unrelated classes or packages.
A common example is a class A depending on an interface I instead of on a concrete class C. Changes to the C class would not affect A's code, nor C would be strictly necessary when instantiating A (in the test suite, or in another project.)
Cohesion means defining a focused responsibility for each class or component: code that does not get together well with the main responsibility should go in another place.
This principle is complementar to low coupling, and to the Single Responsibility Principle.
Not only code that changes for different reasons is problematic in the same unit, but this is also the case for code that changes often in relation to code that never changes. Strategy pattern deals with this scenario, by extracting the code that changes at a faster pace in a separated Strategy object.
An example of poor cohesion in web programming is a class that does both HTML generation and access the database. Due to the different concerns implied here, such a design is almost always problematic.
Defining a variation on a type is a responsibility of the implementation of the type actually used, not of the calling code. When switch blocks are used instead of dynamic dispatch, each piece of client code must deal with the type variations by itself.
Returning to the Strategy pattern, the Strategy object encapsulates the policy to use in a part of the system, and the client code just call a Strategy method without knowing much about what will happen. You'll never see a switch over the type of a Strategy object or one of its fields.
A class that does not represent a real concept of the program's domain, but it is introduced anyway to help applying the other principles is known as Pure fabrication.
Wikipedia here makes the examples of DDD services, a point of conjunction for code that would couple different entities together.
In my opinion, Pure fabrications are often mandatory and mark the difference between a naive programmer and an experienced one. Don't be scared of creating them.
All problems in computer science can be solved by another level of indirection.
Delegation is the heart of an object-oriented application: for a beginner an object-oriented program seems to do nothing as every object delegates to another one, in a neverending chain. Instead, in a well-built application, an object fulfills its responsibility while not entering into the realm of its neighbor, which he delegates the rest of the work.
This is a very broad concept: encapsulating the role of a class into an abstraction (interface or abstract class usually), so that its variations can be expressed by tweaking the implementations of that abstraction instead of by code modifications of the client code. The rest of the program is so protected from the variations in the abstracted away part.
This idea is related to the Open/Closed Principle, and to Dependency Injection. When you practice enough the extraction of roles and abstractions, it becomes at a certain level a second-nature.