- Single responsibility principle: a class should have only a single responsibility
- Open/closed principle: software entities should be open for extension, but closed for modification
- Liskov substitution principle: objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program
- Interface segregation principle: many client-specific interfaces are better than one general-purpose interface
- Dependency inversion principle: high level modules should not depend on low level modules, both should depend on abstractions
https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)
- One codebase tracked in revision control, many deploys
- Explicitly declared and isolated dependencies
- Configuration stored in environment variables
- Local and third party services are treated as attached resources
- Separated build, release, and run stages
- App is executed as a stateless, share-nothing process
- Services are exported through ports
- Scale out via the process model, rely on the OS to manage availability
- Maximize robustness with fast startup and graceful shutdown
- Keep development, staging, and production as similar as possible
- Treat logs as event streams, write event stream to
stdout
- Run admin tasks as one-off processes in the same environment as the app
- Restrict all code to very simple control flow constructs. Do not use
goto
and direct or indirect recursion. - All loops must have a fixed upper-bound, which should be verifiable by static analysis.
- Do not use dynamic memory allocation after initialization: no
malloc
or GC. - No function should be longer than what can be printed on a single sheet of paper, i.e. ~60 LOC.
- The assertion density of the code should average to at least 2 assertions per function.
- Data objects must be declared at the smallest possible level of scope. Do not reuse variables for multiple purposes.
- The return value of non-void functions must be checked by each calling function and the validity of parameters must be checked inside each function.
- Preprocessor use must be limited to the inclusion of header files and simple macros.
- The use of pointers should be restricted to one level of dereferencing. Function pointers are not permitted.
- All code, from start of development, must be compiled with all compiler warnings enabled at the compiler's most pedantic setting. All code must compile without any warnings.
http://spinroot.com/gerard/pdf/P10.pdf
Write programs that do one thing and do it well. Write programs to work together. Write programs to handle text streams, because that is a universal interface.
-- Doug McIlroy
http://www.catb.org/esr/writings/taoup/html/ch01s06.html
- You can't tell where a program is going to spend its time.
- Measure. Don't tune for speed until you've measured, and then only do so if one part of the code overwhelms the rest.
- Fancy algorithms are slow when
n
is small andn
is usually small. - Use simple algorithms and simple data structures, e.g. arrays, linked lists, hash tables, and binary trees.
- Data structures, not algorithms, are central to programming.
http://www.lysator.liu.se/c/pikestyle.html
- Identify and reject your illusions and ideas that don't help.
- Identify and reject the non-problems blocking your problem or keep them but trick them into helping solve the real problem.
- First consider the options that everyone else would reject first, that is where the biggest algorithmic improvement is likely to be found.
- Carefully construct a well thought out solution.
- Optimize that solution design as far as it can go in that direction.
- Identify and reject the illusion that starting over is bad and you are ready to code. Return to 1. with a greater understanding of the issues UNTIL you collect all the good solutions that you can imagine.
- Compare solutions. Construct experiments, benchmarks, simulations or whatever you need to confirm that you found the optimal approach to the problem.
- Continue to return to 1. UNTIL the ideas are "right by design."
- After making the problem look brutally simple by doing the factoring factoring factoring before coding approach the trivial programming task of constructing a one-to-one image of the optimal solution in code.
- Code. Build custom tools if they help. Write code so simple and clear that bugs simply can't happen. Make the code "right by design." Return to 1. UNTIL the code solution falls out.
- Write documentation so simple and clear that bugs simply can't happen. Document the "right by design" algorithms and code. Create a glossary with a description of each word. Have fun at all times. Enjoy the satisfaction of a job well done.
If you do this right the easiest step is 10. If you start at 10 you understand 1% of this methodology.
http://www.ultratechnology.com/method.htm
- Keep It Simple.
- Do Not Speculate. Do not put code in your program that might be used. Do not leave hooks on which you can hang extensions. The things you might want to do are infinite; that means that each one has 0 probability of realization. If you need an extension later, you can code it later - and probably do a better job than if you did it now.
http://www.colorforth.com/POL.htm
- In particular you need to avoid writing code for situations that will never arise in practice. You need to avoid writing code to handle a general problem that you are never going to encounter. I don't need to solve the general problem when I only have to solve these specific cases.
- Don't Complexify. Simplify the problem you've got or rather don't complexify it.
http://www.ultratechnology.com/moore4th.htm
- Use lookup tables for complex decision making
- Use fixed size arrays whenever possible
- Avoid dynamic memory allocation
- Reduce the number of tasks in the system
- Avoid multi-threaded design
- Optimize the design only for the most frequently executed scenarios
- Sometimes searching might be more efficient than hashing
- Use state machines to simplify design
- Use timestamps to avoid running timers
- Use object oriented programming
- Avoid design hooks for future enhancements
- Avoid variable length and bit packed messages
- Reduce message handshakes
- Simplify the hardware architecture
- Prefer general purpose computing platforms over specialized platforms
- Do not use proprietary protocols/operating systems
- Prefer buy over build for software
- Prefer buy over build for hardware
- Prefer designs that lead to more reuse
- Avoid a heterogeneous hardware and software environment
- Consider hardware upgrades to reduce software effort
- Minimize configurable system parameters
- The "0 or 1 or n" Rule
http://www.eventhelix.com/RealtimeMantra/KeepItSimple.htm
- A method of an object may only call methods of:
- The object itself.
- An argument of the method.
- Any object created within the method.
- Any direct properties/fields of the object.
- Don't talk to strangers
- High cohesion, loose coupling
http://thebojan.ninja/2015/04/08/high-cohesion-loose-coupling/
Be liberal in what you accept, and conservative in what you send.
https://tools.ietf.org/html/rfc1122
Protocol designs and implementations should be maximally strict.
https://tools.ietf.org/html/draft-thomson-postel-was-wrong-00
- No prototypes – always maintain constantly shippable code. Polish as you go.
- It’s incredibly important that your game can always be run by your team. Bulletproof your engine by providing defaults upon load failure.
- Keep your code absolutely simple. Keep looking at your functions and figure out how you can simplify further.
- Great tools help make great games. Spend as much time on tools as possible.
- Use a superior development system than your target to develop your game.
- Write your code for this game only, not for a future game. You’re going to be writing new code later because you’ll be smarter.
- Encapsulate functionality to ensure design consistency. This minimizes mistakes and saves design time.
- Try to code transparently. Tell your lead and peers exactly how you are going to solve your current task.