User Tools

Site Tools


Software Engineering and Programming

SOLID Design Principles

S Single responsibility principle A class/method should have only one responsibility.
O Open/closed principle An object should be open for extension but closed for modification.
L Liskov substitution principle A base type should be replaceable with subtypes in each and every situation.
Is-A inheritance is actually Is-Substitutable-For.
I Interface segregation principle Use client-specific interfaces instead of one general interface.
By using small interfaces, you don't force clients to implement more than they need. Prefer small, cohesive interfaces to “fat” interfaces.
D Dependency inversion principle Depend upon abstractions such as an interface or an abstract class. This makes the code less coupled to the actual implementation.

Software Design

  • Inheritance means “is a” i.e., base classes describe what an object is.
  • Interface means “behaves like” i.e., interfaces describe a way an object behaves.
  • Working software is a primary measure of progress. (Agile Principles)
  • The purpose of modeling is to communicate and understand, not to document.
  • The exact format of the design document is less important than the process of thinking about your design. The point of designing is to think about your program before you write it.
  • The key to writing good programs is to design classes so that each cleanly represents a single concept.
  • Organizing the relationship between classes in a program is often harder than laying out the individual classes.
  • Stroustrup: One of the most powerful intellectual tools for managing complexity is hierarchical ordering - organizing related concepts into a tree structure with the most general concept as the root
  • Abstraction helps you to manage complexity by providing models that allow you to ignore implementation details. A class interface that presents a good abstraction usually has strong cohesion.
  • Encapsulation prevents you from looking at the details even if you want to.
  • Nicholas A. Solter “Professional C++”: “Too often programmers jump into applications without a clear plan: they design as they code. This approach inevitably leads to convoluted and overly complicated designs.”
  • Role interfaces:
    • Few members, preferably just one.
    • It's easier to fulfill the Liskov substitiution principle with lots of small interfaces than with few large interfaces.
    • If you don't want a certain feature, you don't implement a corresponding interface.
  • Header interfaces:
    • Old-fashioned .h headers.
    • Extracted from classes.
  • Postel's Law:
    • Output / sender: conservative - give as many guarantees about your output as possible.
    • Input / receiver: liberal - try to be as tolerant of input as possible.
  • Fail Fast: inform the sender as quickly as possible that something went wrong and tell him how to fix it.
  • Returning null value is a bad design decision.
  • Two characteristics of well-designed code:
    • High cohesion
    • Low coupling
  • Nicholas A. Solter “Professional C++”: If you find that you are stuck, you can take one of the following actions:
    • Ask for help. Consult a coworker, mentor, book, newsgroup, or web page.
    • Work on something else for a while. Come back to this design choice later.
    • Make a decision and move on. Even if it's not an ideal solution, decide on something and try to work with it. An incorrect choice will soon become apparent. However, it may turn out to be an acceptable method. Whatever you decide, make sure you document your decision, so that you and others in the future know why you made it.
  • Bohm and Jacopini: All programs can be written in terms of only three control structures:
    • Sequence structure
    • Selection structure (if, if/else, switch)
    • Repetition structure (for, while, do/while)
  • Steve McConnell “Code Complete”: Classes and routines are first and foremost intellectual tools for reducing complexity. If they're not making your job simpler, they're not doing their jobs.
  • Instantiation of objects within a class creates coupling between the class and the objects' types as well as it violates the Open/Closed Principle of SOLID: we need to modify the class in order to accommodate the instantiation of a new object's type. The only thing that our program should be aware of is a common interface shared between all instantiated objects.
  • Fundamental Challenge [Brooks 1987]: “The hardest single part of building a software system is deciding precisely what to build… Therefore, the most important function that the software builder performs for the client is the iterative extraction and refinement of the product requirements… I would go a step further and assert that it is really impossible for a client, even working with a software engineer, to specify completely, precisely, and correctly the exact requirements of a modern software product before trying some versions of the product”.
  • Semantic Interface and Programmatic Interface [Code Complete by Steve McConnell]:
  • Make interfaces programmatic rather than semantic when possible.
  • Each interface consists of a programmatic part and a semantic part. The programmatic part consists of the data types and other attributes of the interface that can be enforced by the compiler. The semantic part of the interface consists of the assumptions about how the interface will be used, which cannot be enforced by the compiler. The semantic interface includes considerations such as “RoutineA must be called before RoutineB” or “RoutineA will crash if dataMember1 isn't initialized before it's passed to RoutineA.”
  • The semantic interface should be documented in comments, but try to keep interfaces minimally dependent on documentation. Any aspect of an interface that can't be enforced by the compiler is an aspect that's likely to be misused. Look for ways to convert semantic interface elements to programmatic interface elements by using Asserts or other techniques.
  • The classes and interfaces that you expose publicly to the outside world are your contract. The more cluttered the public contract is, the more constrained your future direction is. The fewer public types you expose, the more options you have to extend and modify any implementation in the future.
Sequential Approach Iterative Approach
The requirements are fairly stable. The requirements are not well understood or you expect them to be unstable.
The design is straightforward and fairly well understood. The design is complex, challenging, or both.
The development team is familiar with the applications area. The development team is unfamiliar with the applications area.
The project contains little risk. The project contains a lot of risk.
Long-term predictability is important. Long-term predictability is not important.
The cost of changing requirements, design, and code downstream is likely to be high. The cost of changing requirements, design, and code downstream is likely to be low.
  • Iterative approaches are useful much more often than sequential approaches.
  • Specify a clear statement of the problem that the system is supposed to solve before beginning construction: product vision, vision statement, mission statement, or product definition:
    • Define what the problem is without any reference to possible solutions.
    • Make it short: one or two pages.
    • Use user language.
    • Describe the problem from a user's point of view rather than in technical computer terms.
    • Keep in mind that without a good problem definition, you might put effort into solving the wrong problem.


  • Requirements (a functional specification):
    • Describe in detail what a software system is supposed to do.
    • Help to ensure that the user rather than the programmer drives the system's functionality.
    • Keep the programmer from guessing what the user wants.
    • Help to avoid arguments. You decide on the scope of the system before you begin programming.
    • Help to minimize changes to a system after development begins.

The development process helps customers better understand their own needs, and this is a major source of requirements changes. [Curtis, Krasner, and Iscoe 1988; Jones 1998; Wiegners 2003]

  • Specifying requirements adequately is a key to project success, perhaps even more important than effective construction techniques.
  • On a typical project, the customer can't reliably describe what is needed before the code is written.
  • A plan to follow the requirements rigidly is a plan not to respond to your customer.
  • If your requirements aren't good enough, stop work, back up, and make them right before you proceed.

Exception Handling

  • The exception handling pattern is to catch the exceptions at the lowest possible level and pass a return value or a Result class to the higher levels, for example:
    • Throw an exception in DataLayer.
    • Catch the exception in Repository, log it, and turn it to Result. Alternatively, you could catch the exception in a VM's private helper.
    • Return Result to VM.
  • If you still are not able to handle the exception in the client layer, re-throw the exception. It will eventually be caught by the global handler.
  • Wrap try/catch around specific statement(s) only. Otherwise, you won't be able to tell where the exception comes from.
  • Consider using the “fail fast” approach: do not catch exceptions and allow them to propagate to the global level. Log them in the global handler.
  • Use exceptions for stating that there is a bug in your app.
  • Exception messages are aimed into programmers, not end-users. End-users should see non-technical messages.
  • Custom controls rarely require any built-in default values. The default values are supposed to be provided by a client application. Throw an exception if a required value is not provided by the client.

Abstration Design

Quotes from “Professional C++” by Marc Gregoire:

“Experience and iteration are essential to good abstractions. Truly well-designed interfaces come from years of writing and using other abstractions. The best interface is rarely the first one you put on paper, so keep iterating.”

“Don't be afraid to change the abstraction once coding has begun, even if it means forcing other programmers to adapt. Sometimes you need to evangelize a bit when communicating your design to other programmers. Perhaps the rest of the team didn't see a problem with the previous design or feels that your approach requires too much work on their part. In those situations, be prepared both to defend your work and to incorporate their ideas when appropriate.”

“Iteration is worth mentioning again because it is the most important point. Seek and respond to feedback on your design, change it when necessary, and learn from mistakes.”

Open-Source Software

An excerpt from “Professional C++” by Marc Gregoire:

  • Free software. Note that the term “free” does not imply that the finished product must be available without cost. Developers are welcome to charge as much or as little as they want. Instead, the term “free” refers to the freedom for people to examine the source code, modify the source code, and redistribute the software.
  • Source code available. The Open Source Initiative uses the term open-source software to describe software in which the source must be available. As with free software, open-source software does not require the product or library to be available for free.

Using a library under the GPL (GNU) might require you to make your own product open-source as well. Boost, OpenBSD, CodeGuru, CodeProject, Creative Commons License allow for using the open-source library in a closed-source product.

Open-source libraries are usually written by people in their “free” time. As a good programming citizen, you should try to contribute to open-source projects if you find yourself reaping the benefits of open-source libraries. If you work for a company, you may find resistance to this idea from your management because it does not lead directly to revenue for your company. However, you might be able to convince management that indirect benefits, such as exposure of your company name, and perceived support from your company for the open-source movement, should allow you to pursue this activity.

Intellectual Property Rights

When you design or write code as an employee of a company, the company, not you, owns the intellectual property rights. It is often illegal to retain copies of your designs or code when you terminate your employment with the company. The same is also true when you are self-employed working for clients.

Extreme Programming

Characteristics of XP:

  • designed for use by teams of up to 10 people
  • emphasizes iterative development
  • centered on user requirements that drive design

XP encourages:

  • rapid iterations
  • developer-created schedule estimates
  • programming in pairs
  • continuous testing
  • active user involvment

Directory structure

Directory structure for a game and a game engine:

  • Docs - Design documents, technical specs, contracts, milestone acceptance criteria, etc.
  • Media - Art and sound assets in source formats. When assets are imported and packed into game files they go into the Bin folder.
  • Source - The source code. Any 3rd party libraries should go to Source\3rdParty.
  • Obj - Temporary files of build targets (e.g. Obj\Debug, Obj\Release, etc.)
  • Bin - The release executables and the data files (Bin\Data) that are used to create the project (not the source code!). It should everything what is needed to run and test the game.
  • Test - The debug and profile targets as well as test scripts, test utilities, logs, etc. It should also contain the release notes for the latest build.
  • Inc - #include files (for the game engine only). It allows the game engine to be published with only the #include files and the library.


  • Little-endian: LSB is stored at a lower memory address than MSB e.g. 0xABCD is stored 0xCD, 0xAB
  • Big-endian: MSB is stored at a lower memory address than LSB e.g. 0xABCD is stored 0xAB, 0xCD

| a – b | < e rule

Do not compare floats for equality or inequality. Rather, test that the absolute value of the difference is less that a specified small value.

Hashing and Dictionaries

Hashing is the process of turning a key of a data type into an integer. This integer can be used modulo the table size as an index into the table.

Given a key k, we want to generate an integer hash value h using the hash function H, and then find the index i into the table:

h = H(k)
i = h mod N

N is the number of slots in the table.

A dictionary data structure is usually implemented as a binary search tree or as a hash table. In a hash table implementation, the values are stored in a fixed-size table, where each slot in the table represents one or more keys. Finding a key-value pair is an O(1) operation in the absence of collisions.

A collision is a situation when two or more keys end up occupying the same slot in the hash table. There are two ways to resolve a collision, each related to a different kind of a hash table:

In an open hash table, collisions are resolved by storing more than one key-value pair at each index, usually as a linked list. This approach does not impose an upper bound on the number of key-value pairs that can be stored. However, it requires memory to be allocated dynamically whenever a new key-value pair is added to the table.

In a closed hash table, collisions are resolved via probing until an empty slot is found. This approach imposes an upper limit on the number of key-value pairs that can be stored. The main benefit is that it uses a fixed amount of memory hence no memory allocations are needed.

Parallelism and Concurrency

^ Parallelism ^ Concurrency ^

Many cores doing the same thing in parallel Many different things being done at the same time, co-ordinated
Foot race or swimming race with lanes Basketball or hockey game
Getting a task done faster Responsiveness and efficiency
notes/misc/software_engineering.txt · Last modified: 2020/10/03 by leszek