Good Programming
Blitz3D Forums/Blitz3D Programming/Good Programming
| ||
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. |
| ||
Some interesting notation [cough!] notes here. |
| ||
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! |