Unangbangkay

Cohost of @unangbangkay on Twitter

Josh Tolentino | weeaboomer, Gamist
| work: RPG Site, Game Rant, Gamecritics | ex: Siliconera, Destructoid

Contacts in Carrd


discord
unangbangkay
Mastodon
@unangbangkay@mastodon.social
Carrd (Professional contacts)
unangbangkay.carrd.co/

zaratustra
@zaratustra

So the Discourse has tacked once again to, "is Good Code needed for Good Games"? This time, a page of Balatro's source code made it to Twitter and some people laughed and laughed at the if-else chain and some people were like BALATRO SOLD A LOT THEREFORE EVERYTHING IT DID MUST BE THE CORRECT WAY

But there are points for and against Good Code. Good Code is a land of contrasts. First of all, what do you mean by Good Code?

(Note in case it wasn't obvious: When I say Bad Code and Good Code, in both cases, I mean code that works. If you're writing code that doesn't work at all, refer to Knuth or something.)

  • Pretty Code: "It has sensible and consistent names for functions and variables. It’s concise. It doesn’t do anything obviously stupid. It was written by a single person, and never touched by another. It reads like poetry written by someone over thirty." This is a standard if-else chains immediately fail as they inevitably look like a first-grader's effort.
  • Performant Code: This is the code that goes fast. A bit outdated since computers became able to do more than one operation per second, but with an unexpected comeback in Zachtronics games. Expect opcode count and loop unrolling to be mentioned.
  • Enterprise Code: This is code designed to be passed around. Enterprise code will go through dozens of programmers, few of which know each other and none of which talk to each other. Each of them sees the code through a toilet tube, and expects to make changes in their section that won't set the whole project on fire. At this level it's fully acceptable to have the same function copied across three different libraries, each with slightly different side effects, because the alternative is worse.
  • Maintainable Code: Code that makes bug-squashing and extending easy. Sensible naming is just the start. If you want to add a new element to the game, how many files do you need to change? Do you even know the names of all of them?

A good way to cause arguments with other programmers is to choose one of these as Good Code to the neglect of all else.

Next, if i remember, i'll get to the actual subject of this article - when it's the case that you shouldn't do any of these things (and the terrible implications thereof). Stay tuned.


zaratustra
@zaratustra

So, having seen all the wonderful advantages writing Good Code can bring you, why would you ever write Bad Code?

  • You're prioritizing some other form of Good Code. Coworkers picked on me a couple of times for making method names that were over 60 characters long, which I did when the method was a one-off and should absolutely not be reused elsewhere. Similarly, another coworker insisted on passing object ids as untyped integers for efficiency, which was an absolute joy when it came to debug a function taking in four of them at once.
  • You're constrained by the system. Maybe your code is in Lua, which thinks having a nice enumeration is for little babies. Maybe your language has no IDE with an autocomplete worth a damn and maybe you don't want to type 20-character variable names every time. Maybe your senior programmer has decided that Systems Hungarian Notation is the one true naming scheme. Maybe the Man won't let you chain ternary operators in order to fit your entire on one screen, because "it's not company policy to make debuggers want to commit suicide". Compromises are made.
  • You don't know how. Programmers constantly want to do novel things, yet will often refuse to admit they're learning as they code. A programmer will often run into a situation where they don't know how to implement something, let alone how to implement it cleanly.
  • You're in a hurry. Clean, maintainable code takes longer to write than just using the first thing that pops up in your head. If you have to do a hundred tasks by tomorrow or the game won't come out, doing the code equivalent of running a big extension cord through the middle of your living room starts to sound very reasonable.
  • It's not important. You're not particularly in a hurry, but you do have better things to do with your time. Such as, for example, anything else in life other than tidying up a if-else chain that is called maybe once per frame.

Maybe this whole thread should have been called "When to write Bad Code". anyway, next should be, finally, why Bad Code is Bad.


zaratustra
@zaratustra

SO we've established that writing Bad Code is good, efficient and sexy. Which brings the question, why would anyone want to write Good Code? Just to lord it over us poor mortals? The answer is, "NO"

Because each instance of Bad Code you write is a tiny crime. Some of them, you will take their secret to the grave. Some will be found the moment you go public. Some will be found by a coworker (or Future You, which is its own kind of coworker) wondering, "what is that smell" and opening a closet to reveal a skeleton. And some will remain in the walls for the duration of the project, stinking up the place and making everything a little more difficult.

But why are they tiny crimes you ask. And I answer:

  • Workplace Tidiness: (50s safety video voice) Here is Billy. Billy finished his work and set it right in the middle of the doorway. Now everyone has to inch around the big honking mess that Billy made. Fuck you, Billy.
    Work is messy. Even more in programming, where at each step you're designing tools which will be used to implement the next step. Keeping your workbench tidy and well-organized means less of your brain will be occupied with remembering just where everything is.
    (Similarly, because code that actually does things is similar to code that helps you do things, you might be tempted to spend your entire time polishing your tool and mistaking it for movement done towards the goal.)
  • Maintenance: If every regulation is written in blood, every programming practice is written in sweat. At some point, a coder spent a week tracking down a hideously arcane issue to a single misplaced character and decided, never again. After years and years, the coder brain develops a reflex, not unlike that of a parent seeing their child leaning to look at their reflection in the canal. Nothing good can come of this. Bad Code has more chance to become Broken Code, and is harder to fix.
  • Performance: Depending on what language you're writing, Good Performance Code is either superficially identical to other kinds of Good Code, or it is an entirely different beast measured by its own sleek, furious standard. Either way, writing performant code will sometimes require long hard thinking during the project, and is an absolute pain to correct for afterwards.
  • Comprehensibility: Congratulations. You've built your class that uses regular expressions, pointer magic or generic reflection to camel-case the name of every object in the application and it fits in one screen. Your name will be right next to Carmack's in the big high-score table in the sky.
    Now all you have to do is convince your fellow co-workers that your code is actually brilliant and not the text equivalent of white noise.
    Your colleagues want to do their things cleanly, not spend a whole lot of time understanding the deep magic that is your thing.
  • Extension: Sooner or later, the designer will want to replace entire sections of your project because it's just not working out. It will never be something you prepared the code in advance. It is impossible to predict a game designer's mind.
    Similarly, in case you win the lottery and your video game actually sells millions, you might be tempted to sell some expansions. At least in this case you have the option to hire some help and rewrite the entire game in something that supports mobile.
    Legend says some programmers have written Good Code that is so good, they go on to use it on a second project. These stories are mostly unsubstantiated, however.
  • Legacy: In a way, it's very sad that the only accessible version of the Minecraft source code is a compiled-then-decompiled version of something written by a single multi-tasking developer across years -- because, it turned out, Minecraft code became the first serious code an entire generation of prospective programmers looked at. We're all learning from each other - we should all be teaching each other as well.

So what's the conclusion? The conclusion is that we should be understanding of the reasons an unsightly if-else chain is found in production code, and at the same time we should not ignore that Balatro is using decimal fractions as object identifiers what are you even thinking


You must log in to comment.

in reply to @zaratustra's post:

This was my thought too!

Sometimes there's a need to just get something done and is it the most intelligent and elegant way? probably not! does it matter? also possibly not!

It's very easy to criticise after the fact, but there's a value judgement to be made- "how easy is this to do now? will it make life harder or easier for me in the future?" sure, having a custom animation system or a unique set of classes for composing bespoke entities might seem neat but is that going to speed things up further down the line?

It's very easy to get caught in an endless optimisation loop, or crippled by analyisis paralysis, and tbh, there's more than enough half-finished games by indie devs lying by the wayside that should be telling us we need to put more value on the skill of finishing as opposed to some platonic ideal of a codebase.

in reply to @zaratustra's post:

insisted on passing object ids as untyped integers for efficiency

JAIL

(Actually, which language/environment was this in? I'm struggling to imagine how typed identifiers would practically cause any slowdown in a modern JIT/interpreter, much less a compiled language)

in reply to @zaratustra's post:

Legend says some programmers have written Good Code that is so good, they go on to use it on a second project. These stories are mostly unsubstantiated, however.

One time I reused code for player input
Then I stripped it all out and used a library instead

I can’t find fault with that Balatro code. Like, sure there are plenty of “better” ways to do it, but that code is completely straightforward to understand. I have no confusion about what it does. Why waste time rewriting it?

I think it's less that there's anything wrong with that specific example in this context and more that there is not a one-size-fits-all solution to what is the best kind of code to write, because it actually depends on a bunch of factors. A single-person indie game project does not really need to concern itself with a lot of things that bigger multi-person projects do, and that's actually probably fine in a lot of cases. But if the project was bigger/different it could start being a problem. When logic is repeated many times in code, it creates more things that you have to keep track of and update if you ever decide to rework your logic or the way your code is structured. It starts becoming both tedious and prone to errors, requiring a lot of manual effort when something is changed.

But if you're a solo dev trying to get something out the door, that might not be a high priority for you, and something you're willing to accept/deal with. I don't think there's anything inherently wrong with that.

Also, importantly, a lot of projects where this wouldn't fly are ones where the code you're writing is intended to form part of a long-lasting infrastructure where other bits of code will depend on the code YOU wrote working in a certain way and being reliable and efficient. A video game generally isn't like this, but if could be if you're writing an engine or some kind of tool that you're intending other people to incorporate into their games/projects.

The worst I can accuse it of is that it isn't actually straightforward, but slightly deceptive: lots of people have an intuitive reaction that the if chain should be a switch statement or table lookup instead, but they're wrong (true for some if chains, like the one in OP; not true for others, like the handling of joker cards). Any alternative syntax that I can think of would be worse, however.

for me, the major concern with if-then chains is that they're flexible, and therefore unpredictable: it's easy to break their functionality with simple mistakes, or assume two chains are actually one chain, or end up being majorly confused how two nested chains work.