Good Programming

Blitz3D Forums/Blitz3D Programming/Good Programming

Jonathan Nguyen(Posted 2007) [#1]
Hey guys, I was writing a simple doc to keep track of my code formatting style and programming style and decided that it's probably worth sharing with you all. It's a simple thing on variable name conventions with a fair bit on encapsulation and OOP. It's not the most detailed or even the most useful, but I hope for those who are ready to delve into OOP and encapsulation that it'll spark an epiphany in you or something. Some of you guys familiar with OOP and C++ already will find this familiar. Anyway, hope it's worth the read:

COMMENTS

All comments are in the form:
; // Text Here

TYPE PREFIXING

All variables names are mixed capitalization. The first letter of a word in the variable is capitalized and following letters are lowercase up until the next word. Each variable also starts with a letter prefix describing the type of variable (objects do not have a prefix):

b, boolean
i, integer
f, float
s, string
p, pointer (in Blitz, the object/handle system)

One of the useful things with prefixing is that if you create a ini or config reader and save your variables the same way, you'll instantly know what type of data you're reading in your file.

Example:
sCharacterName$, iTreeCount, fBuildingDistance, bIsDebugOn

MEMBER VARIABLES

When anything is called a member, it means that it has something to do with a class and that class only. Member variables must start with an underscore (_).

Example:
Type Car
   Field _sMake
   Field _sModel
   Field _iYear
   Field _fMileage
End Type

FUNCTION WRITING

Make sure you do robust error checking. If you pass an object as a function parameter, check to make sure the object is valid (not NULL). If you are passing an index to an array and you know the array size, check to make sure it is within range! As your program grows, you will be VERY glad that you added robust error checking so that you can track down ghost bugs.

FUNCTION NAMES

Function names follow the same letter prefixing rule. Member functions also have a underscore in front of them (except for the constructor, see below). The only difference is that for a function that returns nothing you use a "v" as a prefix to denote "void".

OBJECTED ORIENTED PROGRAMMING

Each class definition and function definition is preceded by a simple comment. All types come with a create and delete function (constructor and destructor). Function types are then written after the class declaration in order of their action:

1. Constructor (make)
2. Destructor (kill)
3. Search (find)
4. Modifiers (set)
5. Query (get)
6. Action (do)

Example:
; // Account
Type account
	Field _sName$, _sDescription$
End Type

; // oCreateAccount
Function createAccount.account(sName$)
	acc.account = New account
	acc\_sName$ = sName$
	acc\_sDescription$ = ""
	Return acc
End Function

; // _vDeleteAccount
Function _vDeleteAccount(acc.account)
	If acc=Null
		RuntimeError "_vDeleteAccount() passed NULL account."
	Else
		Delete acc.account
	EndIf
End Function

; // _pFindAccount
Function _pFindAccount(sName$)
	For acc.account = Each account
		If acc\_sName$=sName$
			Return Handle(acc)
		EndIf
	Next
	Return 0
End Function

; // _vSetAccountDescription
Function _vSetAccountDescription(acc.account, sDescription$)
	If acc=Null
		RuntimeError "_vSetAccountDescription() passed NULL account."
	Else
		acc\_sDescription$ = sDescription$
	EndIf
End Function

; // _sGetAccountName
Function _sGetAccountName$(acc.account)
	If acc=Null
		RuntimeError "_sGetAccountName() passed NULL account."
	Else
		Return acc\_sName$
	EndIf
End Function

; // _sGetAccountDescription
Function _sGetAccountDescription$(acc.account)
	If acc=Null
		RuntimeError "_sGetAccountDescription() passed NULL account."
	Else
		Return acc\_sDescription$
	EndIf
End Function

; // _fGetAccountBalance
Function _fGetAccountBalance#(acc.account)
	If acc=Null
		RuntimeError "_fGetAccountBalance() passed NULL account."
	Else
		; // Not Implemented Yet
	EndIf
End Function

You might be wondering, why write a function to get a variable when I can just get it? Well, Blitz doesn't support member functions so you won't really appreciate it (unfortunately have to pass every instance). It has to do with something called encapsulation. The idea of encapsulation is that you hide all of the details and inner workings of your class from the user. That way, all the user needs to know is what functions do what and how to use them. The example I provided doesn't really show this... yet. So my account is going to consist of a linked-list of a transaction type. To get the account's balance I'm going to have to write a decent algorithm that will run through all of the linked lists and come up with a balance. The user doesn't care about how I did it, they just want to know that it works and how to use it. So I write a function that does it and they just need to know that it will return the balance properly. Carry this over to even simpler functions like getting the name. That way you hide all of the actual variables you use. Imagine giving this class definition to someone. All you need to tell them is "just use this function to set the description and that one to get it" instead of "the description's variable is called _sDescription, but careful about changing it because in this other function it requires blah blah blah." That blah blah blah is wat we're trying to avoid here. The idea is to keep the "public" side of things simple to hide the "private" inner workings. If you're heading into C++ you'll be seeing a lot of this.

How far can you get with this encapsulation? Well, why not encapsulate your whole game, not just the objects? Usually when writing a game in Blitz you'll write a bunch of class definitions for your objects but your game itself still relies and globals and what not. Instead, why not make a game class where your globals are simply your member functions and your arrays are "Blitz arrays" of a class. Then your regular functions are now member functions. What can you do with this now? You can actually run multiple instances of your game. I know it doesn't sound that useful but can you imagine doing that with the way your game is coded now? If you truly programmed something clean and encapsulated, you should be able to. Now I'm not saying you should have done something like that already; I still haven't but got close with Mechanized Warfare where I actually have a function called Engine() that runs the game. With proper encapsulation the destructor will clean things up. This makes setting up overarching menus a sinch because you simply program a menu that constructs a game and destructs it when its over.

Anyway, I hope this was inspirational. My intention isn't to provide a detailed tutorial on how to program OOP and encapsulation, but I hope I sparked an epiphany in some of you. When I started learning this stuff I had an epiphany and realized that I've been programming the wrong way this whole time. It also got me pretty excited with the possibilities that come with good object-oriented programming.


Sledge(Posted 2007) [#2]
Some interesting notation [cough!] notes here.


Jonathan Nguyen(Posted 2007) [#3]
Thanks for that! That was a really good read. Makes me glad that I haven't written anything with the simple convention above or else I'd have to change it!