November 15, 2012

Some Thoughts about (Object Oriented) Game Programming

Some of my favorite best practices for object oriented programming. Actually, they also apply to programming in general, and no matter if it is business software or a game.

Understandability, Maintainability, Readability
This is the most important practice of all, to avoid:
  • cryptic, chaotic, over engineered and under engineered code
  • bad formatting, no formatting rules, weired naming, thousands of lines per method
  • undocumented code, useless comments, wrong comments
  • code blocks without empty lines to let the reader easily grasp its meaning
  • methods broken into smaller parts and thus hurting understandibility
  • names for variables, methods, classes, packages which are not self-documenting
  • names being too short or too long
  • methods hard to use because of too many parameters
  • errors and exceptions not treated properly, not logged, sent into nirvana
  • keeping users totally uninformed about errors, showing useless information ("an error occured") or a pile of technical messages
  • a cumbersome environment with slow, annoying and error prone development cycles

Knowing all kinds of fancy programming techniques, patterns and the like is not enough. One should always think of how an application will behave when being run in an production environment for years and years - possibly long after the creators have moved on.
What kind of problems might occur ? Will they be easy to track down ? Is the given architecture and environment comfortable to use for enhancements, or will new developers just think wtf and create arbitrary bypasses and thus worsen the situation ?

Use simple solutions whenever possible and complex ones if appropriate.
Follow common sense.

Data Encapsulation
Hide your data from the others. Otherwise harm will be done - sooner or lateer.
Do not use the public scope for class members. And do not mechanically add getters and setters for all members, as that breaks encapsulation as well.
Do not store everything as member variable, just for the case that it might be useful some day. Favor method parameters instead and pass data around.

Static is toxic
There are rare use cases for static methods and class members, exceptions are constants in Java or a collection of stateless mathematical functions.
Stateful methods need to store information somewhere. If they are static, they can only access static members, which in consequence leads either to shared states or to a singleton class design. Both options are seldom practicable and useful.

Static methods are not overridable and as such do not allow to change implementations any time later, neither for the whole code base nor for selected modules. Keeping software flexible and open for changes is very valuable. This is also essential for good frameworks and the infamous game engine.

Remember that any variable marked as public static is nothing but a global variable.
Keep in mind what it means when multiple objects of a class are creatable and some of the class members are static. All objects share those members, they are no longer private to each object, no longer encapsulated into each object. A good way to introduce side effects.Converting to singletons here is most likely not a solution.

Anybody who is starting to learn programming with an object oriented environment like Java should never use statics. Later on, with more experience, one can decide on when to make exceptions to this rule. Otherwise, you won't learn how to set up, handle and maintain object relations and understand the design of existing libraries.

Static is an exception to the default way, you need to add a keyword to your code. There should always be a good reason to leave the main path.
Static is the entrance to a dead end street.

Modules and Dependencies
Split up the code into classes, packages and modules. Limit responsibilities and tasks of classes and methods (Separation of concerns).
Keep relations between classes and packages as low as possible. Stay away from cyclic dependencies.
Use interfaces for communicating between layers, packages or modules. Do not create dependencies across packages into concrete implementations.
Keep interfaces lean, typically do not add lifecycle methods for creating and setting up objects, as those are mostly implementation dependent and are only called at places where concrete classes are dealed with anyway.

OOP != Inheritance
OOP is much more than class inheritance. Setting up an inheritance hierarchy of seven levels is not the way to go. Favor object composition which can be dynamic at runtime as opposed to static compile time inheritance.
Lego brick programming.

Frameworks, Libraries, Game Engines
This is the ultimate challenge. To create something useful for a broad audience, without knowing each and every use case.
Good frameworks give support for use cases the users choose while at the same time do not force any unwanted features and clutter upon them. Features should be selectable which is only possible with a good dependency management (see above) of the whole work.
Classes must be accessible and extendable (no final modifier, no package scope) without breaking encapsulation by making each and every member protected.
Again, object composition is to be preferred as opposed to force inheritance on library users and only give a limited set of overwritable methods (template method pattern).
All of this is very difficult and takes a lot of experience to achieve.
 
Documentation
Eh, what ? Yes, the part we all love the most. Keep in mind that there is never more than one person (at most) who thinks your code is self-documenting, clear as it can be and just ingenious...

What else
A frequent use of the instanceof operator is an indicator of incomplete class design and thus preventing to benefit from polymorphism.

No comments:

Post a Comment