Pandemonium

September 19, 2010

Efficient Development – part five

Filed under: Games Development — bittermanandy @ 6:27 pm

So, as promised: a more positive post, and this one also offers something more concrete than recent posts have tended towards: another component in the Kensei.Dev library, about which I have written before.

I recently found myself needing to add logging to Pandemonium, to aid with debugging. The idea of logging is to write text to the output window and/or a file, to explain what the code is doing. It is then possible the examine the logs to see what happened and when, to identify for example why something went wrong. .NET provides Diagnostics.Trace but it is somewhat limited in scope, so a more powerful logging framework can offer much more control over what is logged.

Initially I planned to create my own logging framework from the ground up, but I suspected that I would merely be repeating what is already available – and indeed about five seconds on Google pointed me in the direction of this. DotNetLog (also known as BitFactory.Logging, also known as Termite) is quite an elegant framework: you can send output to any number of loggers of which there are many different types, and each can be formatted individually. It costs $5 but that’s almost as good as being free, so I wasn’t too concerned by that.

If there is one unfortunate restriction to DotNetLog, it is that it doesn’t work on Xbox 360 (you may have gathered I’m not at all bothered about Windows Phone 7) as it isn’t consciously intended for XNA games. Therefore, I decided that rather than litter my code with references to BitFactory.Logger (all surrounded with #if WINDOWS) I would create a Kensei.Dev.Logger static class, which would be the only area of code that would refer to BitFactory at all. There are a number of other advantages to such a facade, such as those mentioned here (an article from which I borrowed a number of ideas).

Within just a few minutes Kensei.Dev.Logger was up and running, and sending log output to both the console (visible in the Output window in Visual Studio), for monitoring while the game is running, and a file, for analysis later. Generally of course, the console logging would be disabled in your Release build, and file logging would also be disabled but with the option of being enabled via a command line switch, so if your players are having technical problems you can tell them to enable logging, play the game until the problem occurs, then send you the logs.

So far so ordinary – nothing new as of yet. But I had a few further ideas of how this logging framework could be extended to be more useful.

FrameCount and LocalFrameCount

By default, DotNetLog writes out each log entry in a format similar to the following:

[Pandemonium] -- {Kensei} --<Info> -- 19/09/2010 17:13:44 -- Initialised logger.

That’s the application name, followed by the log category, then the log level, then the time of the log entry, and finally the actual log message itself. It’s really easy to write a Formatter of your own (derived from BitFactory.LogEntryFormatter) to display this in a more pleasing format, but what really bugged me was that the time shown just isn’t very useful. A Windows service might want to know the date and time to the nearest second, but for a game such information is simply not very helpful. It would usually be much more useful to track the number of frames since the game was started (or the most recent level change) and log that, instead of the time.

Initially it seemed there was no way to do this, but a quick email to the framework’s authors (and an equally quick reply) showed the way forward. First, I had to define my own FrameBasedLogEntry type, derived from LogEntry:

private class FrameBasedLogEntry : LogEntry

{

    // Constructors not shown

    public uint FrameCount { get; set; }

    public uint LocalFrameCount { get; set; }

}

Then, I had to create my own Logger and override the function that normally created a LogEntry, getting it to create a FrameBasedLogEntry instead, filling in the FrameCount and LocalFrameCount values (with the total number of frames since the game started, and the number of frames since the last level change, respectively).

private class FrameBasedCompositeLogger : CompositeLogger

{

    protected override LogEntry CreateLogEntry

        ( LogSeverity aSeverity, object aCategory, object anObject )

    {

        return new FrameBasedLogEntry(

            aSeverity, Application, aCategory, anObject.ToString(),

            Logger.FrameCount, Logger.LocalFrameCount );

    }

}

Finally, I had to create my own FrameBasedLogEntryFormatter derived from LogEntryFormatter, and tell the various Loggers I was using, to use it for formatting.

private class FrameBasedLogEntryFormatter : LogEntryFormatter

{

    protected override string AsString( LogEntry aLogEntry )

    {

        FrameBasedLogEntry frameLogEntry = aLogEntry as FrameBasedLogEntry;

 

        if ( frameLogEntry != null )

        {

            return String.Format( “[{0}/{1}] [{2}] [{3}] {4}”,

                frameLogEntry.FrameCount,

                frameLogEntry.LocalFrameCount,

                frameLogEntry.SeverityString,

                frameLogEntry.Category ?? “”,

                frameLogEntry.Message ?? “” );

        }

        else

        {

            return String.Format( “{0} – [{1}] [{2}] {3}”,

                aLogEntry.Date.ToShortTimeString(),

                aLogEntry.SeverityString,

                aLogEntry.Category ?? “”,

                aLogEntry.Message ?? “” );

        }

    }

}

Easy! Now all log entries were formatted with the frame count instead of the time, something like as follows:

[47563/1489] [Info] [Kensei] Silver star 42 collected.

LoggerForm

The really clever bit was still to come. You may remember from my earlier discussion of Kensei.Dev.Options and Kensei.Dev.Command that these components offer dialog boxes, so that changing an option is as simple as clicking a checkbox, and entering a command simply involves typing it and hitting Return – all while minimally affecting the game itself. I wanted to do the same for Kensei.Dev.Logger, and furthermore to easily allow the option to disable certain categories from displaying in the logger at all (so you know if the bug you are looking for is in your audio code, for example, you can disable all logging except from the “Audio” category).

You can view my code later to see exactly how I built up the form (using the standard form designer as a basis, of course) but the interesting part was to create a new DialogBoxLogger type, derived from BitFactory.Logging.Logger. Instead of overriding the WriteToLog function, which would only allow me to modify a preformatted string, I overrode the DoLog function, to enable me to work with the LogEntry object itself (really of course a FrameBasedLogEntry). This sent the LogEntry to the form, where I was able to use the Category information to populate a CheckedListBox, and only output the log entry if the corresponding category was checked; and also to change the colour of the log entry to make errors more obvious.
 The Logger dialog shows all logging and allows different categories to be enabled.

I confess, some of the above logging is not completely genuine; but hopefully this screenshot is enough to demonstrate what a powerful and clever tool this is. The whole time I’m debugging my game, I can have the logger dialog running alongside it so I can see exactly what is happening in the game at any one moment. At the same time, logging is still being routed to a log file in the executable folder, so if everything falls apart I can go back and work out what went wrong at my leisure:

[0/0] [Info] [Kensei] Starting LegendsOfXaerolyte version 1.0.0.0 at 19/09/2010 17:50:56.
[0/0] [Info] [Xaerolyte] Hello!
[987/987] [Info] [Kensei] New severity threshold: Debug
[2872/2872] [Debug] [Kensei] Beginning writing Pandemonium blog
[4129/4129] [Debug] [Kensei] Adding links to blog
[6359/6359] [Info] [Pandemonium] Loading game
[8124/8124] [Info] [Pandemonium] Changing level: The River Styx
[8688/8688] [Info] [Kensei] CommandResetLocalFrameCount
[8688/8688] [Info] [Kensei] Resetting local frame count at 19/09/2010 17:53:24
[10315/1627] [Info] [Pandemonium] Loaded level The River Styx
[12559/3871] [Error] [Audio] No sounds available!
[14088/5400] [Info] [AI] Calculating pi...
[18000/9312] [Info] [AI] Calculated pi to three trillion decimal places. Result: 3.14159265358979323846264338327950288419716939937510 ...
[21484/12796] [Debug] [Physics] Player bounced off rock

One possible improvement to the Dev Logger dialog would be to use a tab control, and enable different combinations of categories (and levels) on each tab. Thus you could have the first tab showing all normal logging, the second showing only the “AI” category but including verbose (Debug) logging, the third showing only errors from all categories, and so forth. I chose not to do that at this time, though may come back to it later.

Conclusion

It should be pretty obvious what a useful feature logging is. The DotNetLog logging framework is not only complete in its own right, and easy to wrap in a facade, it is also easily extensible and deliberately structured in such a way that users (eg. me!) can use it in ways that the original authors never expected. Judicious use of inheritance allowed me to take the framework and remould it in a way more appropriate for me, without needing (or indeed, being able) to rebuild the assembly itself.

I make Logger.cs available for download for you to do with as you will, in fact, you can officially do what the fuck you want with it. If you downloaded the rest of the Kensei.Dev code I made available previously it should fit right in with minimal effort (I have tweaked Kensei.Dev a bit since then, but not in any major way as far as I can recall); if not, it should be fairly simple to strip out the references to other Kensei.Dev components and/or replace them with your own equivalents as you see fit. Enjoy, and as always, please leave a comment if you have any feedback, questions or suggestions!

September 17, 2010

Windows Phone 7? Schmindows schmone schmeven

Filed under: XNA — bittermanandy @ 12:51 am

WARNING – rant follows. Those of a sensitive disposition should look away now.

I know this blog has lain dormant for long, but I’ve been working on my game just lately and really getting back into XNA. The chance to work with a halfway sane codebase has been a real relief from my day job, and I’ve really been enjoying it. So I feel bad about this rant, but I have to get this off my chest. It is undeniably one-sided and filled with a certain amount of invective; if you don’t like that kind of thing, it might be an idea to skip this article. There are no coding tips in this article, just heartfelt opinion from an angry man. You have been warned.

XNA is a wonderful and glorious thing of great beauty. It makes game development easy on Windows and more-or-less straightforward on Xbox (though with close-to-zero financial benefit from what I gather). Today XNA Game Studio 4.0 has been released, and what’s the one thing that they keep going on about? Windows Phone 7.

So much so, in fact, that XNA is now part of the Windows Phone Developer Tools. You can’t get away from it! And if you were to visit to the XNA Creators Club website (the first link up there) you’d be forgiven for thinking that it’s the Windows Phone 7 Gaming Creators Club website. Where the hell is all the Windows and Xbox stuff? It’s gone – oh no wait! There’s one tiny little box in the right-hand column that you have to be actually looking for to even notice.

It’s crystal clear where Microsoft are going with this. Windows Phone 7 is their target platform for XNA (Zune appears to have been quietly taken out and shot – hey Microsoft, you can’t create an international brand if you only release it on one continent) and the stuff that is actually cool, easy Windows and Xbox 360 development, has been reduced to nothing more than a forgotten sideshow.

Understand this very clearly. I am a Microsoft fanboy in any meaningful sense of the word. I even still use Internet Explorer despite the fact the whole world is telling me Firefox is better – hey, I like IE, OK? I do. I even worked for Microsoft for getting on for five years, and I enjoyed it, and in another life I might have found a way to stay.

But  here’s the thing. I owned a Windows Mobile 5.0 phone, and it should have been amazing. But that thing was pure hell. Simple tasks – like synching Internet Favourites – failed at the most basic level (one would disappear every time the phone synched, until they were all gone). As a smartphone it was supposed to run my life for me, but when half of my calendar appointments lose an hour if they are in the morning during daylight savings time, with the result that I no longer know when my appointments are, how is it realistically going to do that? That phone, that mobile operating system, that should have dominated the world, instead was a half-assed pile of steaming cow turd that failed on just about every level. I spent hours on the Connect forums for weeks trying to get that phone working, and it just didn’t.

Then the iPhone came along, and it looked really, really nice, but it’s Apple, and I’m a self-confessed Microsoft fanboy. Nevertheless my contract had expired so I could finally get a new phone – but it’s OK! I was told. Windows Mobile 6.1 is much better than 5.0! So (knowing I was making a mistake, but there it was) I got a Windows Mobile 6.1 phone… only to find that not a single one of the issues I had reported in Windows Mobile 5.0 had been fixed. Not one, literally. So again I spent hundreds of hours on the Microsoft support forums trying to sort things out, only to be told in every case “oh yeah, I never use that feature, it doesn’t really work properly”. And it was a two year contract that I couldn’t afford to buy my way out of, so I was stuck with it, and it was the worst phone I have ever owned. I could sit here and write for hours about what made it so bad, I really could.

Happily, I’ve just got an Android (phew, I avoided Apple!) and it’s a thing of beauty. It’s miraculous. I can actually open an internet site in the internet browser and it will actually render! If I then save it as a bookmark, I can come back a week later and the bookmark is still there! Doesn’t sound so amazing? You’ve obviously never had a Windows Mobile 5.0 or 6.1 phone then, because you’d appreciate these things! But now, Windows Phone 7 is on its way… well, no way on Earth was I waiting for that. Not a chance. I have spent hundreds of pounds and hundreds of hours of my life trying to work around basic bugs in Microsoft phone operating systems, and I am very well aware that WP7 is a complete restart compared to WM5 and WM6 (though that didn’t help the KIN…) but sorry, I simply don’t believe that Microsoft know what sane human beings want out of a phone, any longer. I gave them two chances and was punished for it with bug-ridden software and total apathy on their support forums. There will be no third chance. (I am also fairly sure that I am far from the only one to have been so badly burned by WM5 and WM6 – meanwhile, the rest of the world has an iPhone or Android – which by the way now have such magical futuristic features as copy-paste and multitasking, and the excuse “iPhone didn’t have it at launch four years ago” cuts no ice with me).

Long story short: I’m a Microsoft fanboy and even if Windows Phone 7 performed fellatio on demand, I still wouldn’t get one. No non-MS-fanboys are even aware that it’s an option, or if they are they’ll probably get confused with the old WM5 and WM6 phones which were awful. And yet Windows Phone 7 has become the overwhelming focus of XNA. I can see why MS might consider it strategically desirable but this upsets me. As far as I can tell there haven’t been many Windows and 360 features lost in XNA 4.0 (…right?) but if all we’re going to get from now on is Windows Phone 7 shoved down our throats every time we visit the website or the forums, and if all the new features the XNA team are working on are heavily WP7 focussed, XNA itself might get dragged down with Windows Phone 7 when it dies, and that will be a tragedy.

There. I said it. I told you this post would be something of a diatribe, and it got undeniably emotional at times, but I’ve spent the last several years crippled by a godawful mobile phone operating system and that’s the kind of thing that inspires nerd rage. XNA could really make a difference to game development now and in the future, but not if it’s so woefully mistargeted at a platform even Microsoft fanboys have no interest in.

That the next article will be less of a rant and much, much more positive. Pinky promise.

September 10, 2010

What is good code?

Filed under: Personal,Tools and Software Development — bittermanandy @ 12:11 am

I’ve been thinking a lot about good code lately, if only because I’ve been stuck in the unfortunate situation of having to deal with bad code. Without going into the gory details, the people who wrote the bad code were convinced it was good so I had to spend a lot of time and energy explaining why it was not good, and, therefore, what good code is. (With indifferent success, it has to be said; there are none so blind as those who will not see).

A digression: my plan when I started this blog was to keep the articles frequent, regular, game-focussed and specific. Obviously things have been neither frequent nor regular for some time now, and this article continues the ‘recent’ trend of being neither game-focussed nor particularly specific. What can I say? Plans change, life circumstances change, and work on Pandemonium (the game) is on hiatus; at least this isn’t another “how to get into the games industry” article (which is for a blogger what crates are for games designers). I hope you still find it interesting, and as always feedback is very welcome and gratefully received.

So: what is good code? Can we even place a value judgement on code as “good” or “bad”? After all, there are very often many different ways to achieve the result you’re looking for; code is part art and part science, and there’s not a single non-trivial program in all of coding history that is entirely unable to be improved in any way. (Even “Hello, world!” could be localised…). Well, to consider it from the converse point of view, if code crashes or gives the wrong results, it must be bad code; therefore, it is reasonable to conclude that if it ‘works’ (however that is defined), it may be good code. There is however much more to it than that, as we shall see, and there may often be debate, discussion, and extensive philosophising as to exactly what is “good”.

The following list may not be exhaustive, though I think it’s a pretty good start. In general, each item is given in a rough order of priority, those things that I consider most important listed first – but it is critical to understand that what is “important” can vary greatly depending on context. For example you will see that I normally prize readability ahead of performance, but if your profiling reveals that one function is dropping your frame rate from 60Hz to 10Hz, you have no choice but to optimise it, even at the expense of readability.

Good code is…

…correct. It is amazing how often programmers will miss this one when discussing good code. Games programmers, in particular, have an unnerving tendency to mention “fast” as the first thing they think of when talking on this subject. This is a nonsense. The code must do what it is intended to do, correctly, or else it cannot possibly be good.

There is an important implication here. Firstly, the phrase what it is intended to do implies that good code begins with requirements, specification and design – all before a single line of code is written! Exactly how you generate the code design is itself a subject worthy of lengthy discussion, but outside the scope of this article.

Consider: correct isn’t really enough. Provably correct is much, much better. More on this later.

…readable. How long does it take to type enough text to fill, say, a page-long function? Probably only a few minutes. Obviously there’s a significant amount of time invested in working out what to type, but it doesn’t take long. However you are certain to need to read it later – to review it before you commit it into source control, when QA find a bug, when you need to explain it to a colleague, when a colleague needs to understand what it does without you there to explain. Code must therefore be written to WORM – Write Once, Read Many, and I think this is the second most important requirement after correctness.

What contributes toward code being readable? There are many factors – judicious use of whitespace, sensible function and variable names, short functions, use of good patterns and avoidance of antipatterns – but probably the most critical factor is consistency. I don’t really care if you use three spaces or four to indent, but in the name of all that is holy don’t use three sometimes, four other times and five other times still! Style guides, automatic formatting, and tools like StyleCop are helpful in ensuring consistency across a team – but it’s probably more important to stay consistent within a file. So if you’re editing someone else’s code, be sure to match their style. If things are really bad, set aside time to refactor later; but otherwise, match what is already there.

…testable. This requirement is one that has been bubbling up my list of priorities over the years, such that I now consider it one of the most critical factors in “good code”. I have always believed in the adage that “if it’s not tested, it’s broken” – in other words, unless you know and have proved that the code is correct (responds correctly to good input and fails elegantly with bad input) there’s probably some corner case you’ve missed and it will come back to bite you. I used to assume that sufficient QA coverage would be enough. Not any more – code must (wherever humanly possible) be covered by automated unit and soak tests, which must be added to with every bug found and fixed. This is a lesson I have learned through bitter experience. The codebase I am currently working on is untested and bordering on untestable, and it is almost impossible to make changes (even fixes) without breaking something subtle – and there’s no way of catching that subtle breakage until it is too late (ie. the customer has been affected by it).

Interestingly enough, as unit tests are still relatively new to me, I’m still exploring methods of doing it with which I am comfortable. I definitely have more to learn here. I would also add that I do not strictly practice Test-Driven Design myself, though I can see that it might a good idea in principle; however, even though I don’t currently write the tests before the code, I now always consider how the tests can be written while writing the code (which isn’t TDD but it’s close enough to see it on a sunny day) and I think every programmer should do at least the same.

…well-documented. By this I mean not only separate documents listing the requirements, and user guides, and such like; but also comments within the code itself. In general, code should be self-commenting: CalculateDamage() is a better function name than f(), and RemainingHitPoints is a better variable name than hp. In both cases, it is easy to infer what the code does. Comments should not normally describe what the code does, or even how (both of which should normally be understandable from the code itself unless it is highly optimised), but they should be used to explain why the code does what it does in the way that it does it. I have heard it suggested that every comment should contain the word “because”. That’s not a bad guideline.

…robust and reliable. I had a disagreement with someone (a non-coding manager) a while back when he contended that a “genius coder” was someone who had a blazingly brilliant idea, implemented it rapidly, then moved on to the next blazingly brilliant idea, even if the first idea wasn’t completely finished yet – as other coders could then take up the slack. I reject this suggestion utterly. A real coding genius, a guru, a free electron – call them what you will, we all know the kind of programmer I’m talking about, and I don’t claim for one second to be one myself – such a person does indeed implement blazingly brilliant ideas but they make sure it works.

Good code doesn’t crash (for starters). Neither does it scribble over random areas of memory, trash the stack, or give unpredictably different results for the same input. Indeed, restrictions on input are clearly specified, and assertions and/or error handling are used to anticipate bad input and deal with it appropriately (good code is usually tolerant of bad input unless there is a reason for it not to be, such as performance or security). When good code is used by other people, they can be confident that it will work as advertised, and not throw any unexpected spanners in their works.

…maintainable. The simple truth of the matter is this: all code has bugs. At some point, you (or someone else) will need to come back to your code and fix those bugs. Or, you may need to extend or change it to reflect changes in specifications. Do yourself (and others) a favour, and write your code in such a way that it can be easily maintained.

Happily, if your code is readable, well-documented and covered by unit tests it will probably be easy to maintain, but by explicitly remembering that you will probably have to come back to this code while you design and write it, you can make decisions that will make maintenance even easier.

…extensible, flexible and reusable. Once you’ve gone to all the trouble of writing code, do you really want to put yourself through the hassle of writing it again next time you need to do the same thing? Or writing something almost the same but that varies in some minor particular? I would hope not. With good code, it is possible (perhaps using templates or generics) for other people to use the code for purposes that the author may never have expected, except insofar as he deliberately made it extensible. With good code, it is possible for other people to write code that this code then uses, even though it was written first. A study of good software libraries (perhaps most obviously the STL) is enlightening as to how this can be achieved, and how effective it can be.

There is, of course, a risk of over-engineering – remember YAGNI (You Ain’t Gonna Need It): don’t waste time writing code you’ll never need. As always, this is contextual. An IDE like Visual Studio benefits greatly from a plug-in system that allows other people to write software to extend the software itself. Is it worth writing a plugin system for your own game editor? I suspect not.

…efficient, performant and scalable, in terms of memory, CPU, system resources, processor/thread count, internet bandwidth… as much as it’s nice to be able to pretend such things are infinite sometimes, in truth of course they are not. Good code is not wasteful with such things, and makes promises about how much of each it requires (for example sometimes, you can use more memory to make things easier for the CPU; but on an embedded system that memory may not be available, so an understanding of the target platform is required).

In my experience, performance concerns are most often addressed during the design stage, but when it comes to writing the code itself, using appropriate algorithms and data structures (alongside basic guidelines like avoiding allocations/garbage, etc) is usually all that is required. In my experience and by my estimation, 90% of code is not performance critical and so long as you use a sensible algorithm, that will be sufficient. If you do not understand algorithmic complexity, you are not a programmer – whereas an understanding of the effects of branch misprediction, cache misses, false sharing etc. is something that most coders don’t need to spend too much brainpower on, most of the time. (There is still that 10% where those kinds of things do matter, of course; and games programmers find themselves in that 10% more often than most so they tend to have a skewed view of this).

I’m tempted to write considerably more under this heading, but I’m going to stand by my claim that most of the time, “reasonably performant” is enough to qualify for good code. Don’t throw away performance needlessly and you’ll usually be fine.

…secure. I have been lucky enough in my professional career that I have usually been working on products that do not have to worry overmuch about being hacked. Console games and proprietary custom software for a specific company are not usually prime targets for hackers, who are more likely to aim for websites, operating systems or “serious” software where they can either obtain valuable data or cause havoc for lulz.

Or so most people suppose, anyway. I remember breathing a sigh of relief shortly after the release of Kameo because I’d written (among other things) the savegame code for that game, and it was the savegame code in some other game (I forget which, now) that allowed one of the first successful hacks of the Xbox 360: someone managed to edit a savegame file in such a way that the code read off the end, and hey presto, they found a way to launch pirated games. Although I have read a bit about writing secure code I’m sure this is something I could improve on, and I suspect I’m not alone – good code is secure, even if the consequences of being insecure don’t involve giving away people’s credit card numbers.

…discoverable, by which I mean that when someone else comes across your code for the first time, they can grok it quickly and easily. This is something I’d not consciously considered until recently, which is odd given that so many of the problems I’ve been having with the current codebase have been a result of finding that I need to investigate some code, and immediately thinking “WTF is this doing!?”. Had the code been discoverable, the last few months of my career would have been considerably easier. Of course, again, if your code is readable, tested, well-documented and maintainable, it should probably be straightforward for your team-mates to pick it up. But it is always a good idea to think to yourself, “if a reasonably intelligent, reasonably experienced programmer (who was new to this project) had to debug this code, would it take them long to figure out what is going on?”

…simple. Sorry, I’ve had to go back and edit this post because somehow I managed to miss this the first time around! Good code is simple. Even complex good code is comprised of simple building blocks. Good code hides or cuts through the complexity in the problem, to provide a simple solution – the sign of a true coding genius is that he makes hard problems look easy, and solves them in such a way that anyone can understand how it was done (after the fact). Simplicity is not really a goal in its own right, though; it’s just that by means of being simple, code is more readable, discoverable, testable, and maintainable, as well as being more likely to be robust, secure and correct! So if you keep your code simple (as simple as possible, but no simpler), it is more likely to be good code – but that is by no means sufficient in and of itself.

Well, I reckon that’s probably enough to be going on with. Many of the above considerations are worthy of an article in their own right (not that I intend to write such articles any time soon, or indeed at all) but I think I have written enough for tonight! Have I missed anything? Have I got anything in the wrong order? (I guarantee that some people will argue performance considerations should be higher up the list, and in some scenarios they would be correct, but as a rule of thumb I think the above order is generally best). Is there anything I’ve listed that is not actually a requirement for “good code”? Does anything need further clarification? Let me know, in the comments.

The Rubric Theme. Create a free website or blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.