Four founding principles of object oriented design:
Organise like themed concepts into one unit (class) that performs one task or a group of similar tasks. These units often mimic real world objects, for example an employee class may have a name attribute and a promote method. Encapsulation hides implementation details from the consumers, simplifying external use.
The best definition of abstraction I've ever read is: "An abstraction denotes the essential characteristics of an object that distinguish it from all other kinds of object and thus provide crisply defined conceptual boundaries, relative to the perspective of the viewer." -- G. Booch. Encapsulation and Abstraction are closely linked and used together. Referring to an object using an appropriate level of abstraction allows grouping of different objects together and treating them alike.
Describes various levels of abstraction of an object. A cat class could inherit from Feline, which could inherit from Mammal which could inherit from Animal etc. Each of these levels can describe attributes and actions common to all children that inherit.
By grouping different objects together that all have a common abstract parent actions can be performed on all the objects at once.
The Seven Fundamental Principles of Software Design (more classical / old school):
The Reason it all exists.
A software system exists for one reason: to provide value to its users. All decisions should be made with this in mind. Before specifying a system requirement, before noting a piece of system functionality, before determining the hardware platforms or development processes, ask yourself questions such as: "Does this add Real Value for the users of the system?" If the answer is "no", don t do it. Remember to think about it from your users point of view.
Keep it simple.
A good software design should be simple, elegant and exactly meet the requirements, no more, no less. Creating a simple design is actually harder than creating a complicated one, and takes more time. Don't use this as a security blanket to not learn new skills however and stay current with available tools.
Maintain the vision.
A clear vision, project scope and requirements is absolutely essential. A focused concept of what the system does at a high level helps give clear focus of each smaller component's. The system should not be stitched together like a patchwork of incorrectly fitted puzzle pieces.
What you produce others will consume.
No system is ever used in a vacuum. Someone will be attempting to use it, without instruction. When was the last time you read a software instruction manual? No one reads these anymore. Your software must be intuitive and discoverable. Sometimes your users are low skilled PC-users who will be using a UI to interact, other times they are developers consuming your API. Consider your audience.
Be open to the future.
Software written today will most likely far outlive today's hardware. A strong software design will use the best technology and tools available to ensure the design will still be able to make use of tomorrow's hardware. All software projects should include time to ensure discovery of new tools and appropriate tools.
Plan ahead for reuse.
Write small reusable chunks of code. Each piece should be tightly themed and only concerned with one job. Consider not pushing this too far however, complexity will result. Better to have a small amount of redundancy over excessive complexity.
Developers are detail people. Each step along the way keep zooming out and clarifyng the big picture.
Over time I have found these principles to both save time and clarify and provide clean software design, in no particular order.
Don't fight the tools.
If the tool does a job in a certain way, it will always be easier, faster and more maintainable to just suck it up and use the tool in the way it was intended to be used.
Prefer generating straight forward code over a small amount of complex multipurpose generic code. A good example of this extensive use of reflection, rather prefer writing a code generator for use at design time that will generate all the permutations that the reflection would otherwise have catered for. This gives an easier learning curve when looking at the resulting code and makes debugging far easier.
Always challenge beliefs, look for facts and proof.
If you think doing something one way might be better, find out why and prove with facts. Or at least know you have proved it before and can again if need be.
Get comfortable with Unit Testing.
Its here to stay and will help you prove things quickly without having to write a great deal of test harness applications with a UI.
Don't repeat yourself (DRY). Don't write the same piece of code twice in different places.
Less code equals less testing, less bugs.
Don't over engineer things and don't write code you are not using today.
Patterns over technology.
Always look for established modern patterns. New technology is good but if you can't use it to implement a well suited pattern, forget it.