Search Unity

The point where you cease to understand how your code works

Discussion in 'General Discussion' started by keithsoulasa, Jul 16, 2012.

  1. Arowx

    Arowx

    Joined:
    Nov 12, 2009
    Posts:
    8,194
    If you take every binary decision point in you're game then

    IF / Perms
    1 = 2
    2 = 4
    3 = 8
    4 = 16
    ect

    So 10 if conditions and you have 1024 perms

    Throw in some switch case statements and a few loops and boom off the charts.
     
  2. GibTreaty

    GibTreaty

    Joined:
    Aug 25, 2010
    Posts:
    792
    Sometimes I'll have an epiphany and start a project to fulfill that idea. I could spend hours on it; non-stop coding all day. But if I stop and take a break for a day and then look back at the code, I derp out and it takes me awhile to get back any coding-momentum.
     
  3. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    This whole question speaks to me of the idea of your code not being clean enough that it is clear.

    Here's some things I've picked up that help me a lot:

    Single Responsibility Principle
    A block of code should do one thing, do it well, and do that thing only! If you have a method fulfilling multiple responsibilities, consider breaking out those responsibilities into smaller methods that this method calls. You'll find that it reads a lot easier when you're coming back to it in six months, and have forgotten half of the technical details of the project.

    Salient Naming
    Naming stuff is one of the two hardest things you can encounter in Computer Science, but here's a tip to make it easier: if your name dosen't pass the 'five year old' test, it needs to be changed such that it does.

    What is the five year old test? Consider a hypothetical five year old. Attempt to tell this five-year old what this class/method does, in the simplest language you can muster. If that simplest language does is not close to the existing name of your class/method, it's probably an indescriptive name. Consider changing it so that it's closer to your 'five-year-old's' description of the logic.

    Whitespace is not bad
    Inside of a method, ideally you have very little code - after all, if you're following the Single Responsibility Principle, you've got pretty short methods. However, in a method, it's often a good idea to group lines of code by relevance to each other, by interjecting whitespace between logical groups of operations. It's not like the compiler is going to keep it anyhow; remember, you're writing code for other humans, not the computer!

    Comment on algorithms, not on concrete details
    I have coworkers who believe that code should be 'self-documenting', but I find in practice this can be a bit of a stretch. What I find to be more effective is, in combination with short, well-named classes and methods, to leave comments about the general algorithm that is being used to solve some problem. This lets future you or future teammate know how you're solving said problem.

    What you shouldn't do is comment on some framework method. Modern web browsers have bookmarking capabilities; I consider compiling your own code reference in your browser so that if you get fuzzy you have a one-click reference to that one function you used once upon a time that helped you with [insert problem here.]

    I hope these help :)
     
  4. eskimojoe

    eskimojoe

    Joined:
    Jun 4, 2012
    Posts:
    1,440
    Visual Studio + UnityVS + Reshaper + Code to Flow chart + UML add-in.


    Using this, you can easily understand huge swathes of code.
     
  5. darkhog

    darkhog

    Joined:
    Dec 4, 2012
    Posts:
    2,218
    I've found out that "Captain Obvious" comments help very much. Even if those may be bit annoying at times, like in following case:

    Code (csharp):
    1. //after adding 2 to 2, 'b' should be 4
    2. int b = 2+2;
    when you have many parts of code, it'll greatly help when you get back after some time. Currently I'm going through "comment work" for my project.

    Also remember to indent and do it right! Do NOT indent with spaces, use Tab for that. Reason? Someone on your team (or even future you after change of preferences) may prefer indentations 4 spaces long while someone other things only 2 spaces are needed.

    With Tab characters, you just need to set new "Tab width" in settings of your IDE, with spaces indentations you'll either fight over whether you want 2 or 4 spaces long indents or code will change into unreadable mess (because some parts will be indented with 2 spaces while others with 4). Bad outcome in either case.
     
  6. Brian@Artific

    Brian@Artific

    Joined:
    Jun 25, 2013
    Posts:
    35
    + consistent use of XML comments - I like it when IntelliSense tells me what I was thinking last month. :)
     
  7. goat

    goat

    Joined:
    Aug 24, 2009
    Posts:
    5,182
    That's typical for event driven code...you have to be very careful what gets done where and when.
     
  8. goat

    goat

    Joined:
    Aug 24, 2009
    Posts:
    5,182
    Sorry, for me it's 4 space tabs and to be Captain Obvious if you don't like it your IDE will redo it for you.
     
  9. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,051
    Those type of comments are not helpful, you're doing it wrong. Comments are used to help elucidate what the code is doing not how the language works.

    And as goat pointed out code format is trivial. Ideally a team should agree on one to work well together and to keep merges simple but really not a huge deal. Code style/pattern, that is a different story. ;)
     
  10. Velo222

    Velo222

    Joined:
    Apr 29, 2012
    Posts:
    1,437
    I'm getting to that point too OP. I feel your pain. The ONLY thing that keeps me from completely forgetting what a code block is doing (or what functions its using and related to) are comments. After getting burned a couple times where I come back to code and for the life of me cannot remember why it's there or what it's even doing, I now try to comment every function, and every set of variables into some categories. At least then I might have a chance at remembering what the variable or function was related to, even if it's not exactly commented correctly.

    Without comments, I'd have to rewrite my whole game probably, because I wouldn't remember what does what or why it's where it is in the script lol.

    I'm not looking forward to having to add a huge other system to my game, because my game code just keeps growing and growing and it gets harder and harder to keep it organized lol. I'm also just one person working on my own game.
     
  11. squared55

    squared55

    Joined:
    Aug 28, 2012
    Posts:
    1,818
    With basic stuff like player movement or weapons, I hardly ever use comments because it's pretty self explanatory. But when it comes to more complex stuff, like navmesh generation, the only thing that lets me understand what's going on is the comments. I've taken to not only explaining what a function does, but how I derived it. For example:

    Code (csharp):
    1.                        
    2. ////////////////////////////////////////////////
    3. //Eqn of a plane
    4. //= ax + by + cz = d
    5. //= a(x1-x2) + b(y1-y2) + c(x1-z2) = 0
    6. //
    7. // Y ISOLATED
    8. // y = (ax-ax0 + cz - cz0 - by0)/(-b)
    9. ///////////////////////////////////////                
    10. // y = (normal.x*basePointArray.x - normal.x*vertices[0].x +
    11. //      normal.z*basePointArray.z - normal.z*vertices[0].z -
    12. //      normal.y*vertices[0].z)/(-normal.y)
    13. //         
    14. //ALT
    15. // y = (a(x-x1) + c(z-z1))/b-y1
    All that for 1 line of (essential) code.

    Then there's the fact that the default Unity GUI practically requires comments, as every line looks the exact same.
     
  12. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Yeah, these are pretty solid bits of advice, all of which have been second nature to me for a while and all of which I strongly recommend.

    It's worth noting that single responsibility doesn't apply just to methods/functions, but also to classes and variables. Why make a monolothic Component if you can instead make two or three smaller ones? Smaller components are easier to debug and offer more reusability.

    Another one I like is sticking to consistent solutions wherever possible. It cuts down on required brain power when re-familiarising with code if you can look at how things are done and see that it's mostly the same. it makes code navigation and comprehension much faster because it allows the reader to build (and rely on) expectations. Also, if a part of the code doesn't conform to that expectation (and isn't commented appropriately) you know to pay special attention to it. It also potentially allows for some code re-use.

    Also, consistent assignment of responsibility, and breaking up of responsibilities when they become large. Keep asking "what is this class/method/variable for?" and don't be afraid to re-factor things into more or fewer parts depending on where the answers lead you.
     
  13. HarvesteR

    HarvesteR

    Joined:
    May 22, 2009
    Posts:
    531
    Heh, reading back on this thread, there's a reply I wrote over a year ago.... Things have certainly changed, and the way I code has definitely changed too (hopefully for the better).

    Gone are the days of hacky-implementation-to-throw-away code, I've "discovered" a set of rules that I follow that have been most helpful for producing clean, re-usable, flexible code, that hopefully will also make sense to future-me when I come back to it several months from now.

    Rule 1: Avoid Scope-leaking as if it were the plague.
    "Scope-leaking" is what I call when a method or class depends on stuff outside its own scope. Of course, it's not always possible to avoid this altogether, but the principle is that you should always write methods and classes such that they contain as much of the information they need to work with inside their own scope. This in the long run ensures that the method you've written is usable again in different contexts, because it is a self-contained piece of logic that does one discrete job (which leads to rule 2).

    Rule 2: Decision Makers and Question Answerers.
    I've grown rather fond of this rule, because it's very simple to keep in mind while coding. Basically, it postulates that a method may do one of two things, never both. It may either be a method that makes decisions, based on input parameters, or it may be a method that answers a question. Let me explain:

    Question-Answerers:
    A method that answers a question is generally more useful, so we'll cover these first. Question-anwerers are methods that answer some question, like "what is the situation of this object?", given as few input parameters as possible, and most importantly, without modifying any of them. This last bit is critical to having clean code. If a method that returns you a value also modifies something, then it's going to be doing something you're not aware of from the caller's side, which then leads to bugs and spaghetti code.

    Decision-Makers:
    Sometimes you can't avoid to need to write code that actually does stuff. That is, you might need for instance, a method that updates UI controls for an object's UI. That method then must be void and take all its input from parameters or its parent class' scope (don't leak the scope any further, see rule 1). The rule for such methods is that their logic should be as 'linear' as possible. Or, if it must have conditional code paths, then the conditions for those must ideally come from the output of a question-answerer method. That allows you to later change your mind about the criteria for that condition, and all cases (decision-makers) where that condition is used are blissfully unaware of the change.


    Rule 3: If you can describe it as a noun, it should be a class.
    This is my way of doing code architecture on the fly (or as close to that as possible), which I've found to be really helpful over time. It essentially requires you to look at what you want to do, and figure out what the 'nouns' involved in the process are. Chances are, if you can give something a name, no matter how unphysical the thing is, it will probably be a good idea in the long run to make that thing a class. Yes, it does mean more code to write for something that initially might not seem worthy of its own class, but later, you might want to add something else to that very 'thing', and having a class to represent it will make that process a lot easier. Making conceptual 'nouns' into classes gives your project room to grow, without bloating.

    Rule 4: Learn Interfaces, and use them whenever possible.
    This is kind of self-explanatory, and it'll probably help out in the long run. I was taught at first that if something shares elements with other things, then those things should extend a common base. This isn't always the case. Sometimes what you want is for those separate things to just share a piece of common functionality, and for that you need an interface. Say you want your ships in your ship game to be targetable. You could of course add the targetable behaviour to the 'ship' base class, that would probably work... But what if you later want to be able to target bits of scenery too? Or a ship's engines specifically? If you have an interface to provide the answers (see rule 2) to drive the the targeting logic, you can treat all those separate objects, Ships, SceneryProps, and say, ShipEngine as 'ITargetables', and your targeting radar need not care what the thing actually is, just that it is targetable so you can lock-on and fire stuff at it. Same also goes for taking damage later, if you have an IDamageable interface, life gets a lot easier.


    As you may have noticed already. the ideas behind these rules aren't any different than the guidelines that were suggested a few pages back, about single-responsibility and clean naming and such, but this is the way I think about those concepts in practice. To me this way of thinking makes the most sense when you're actually getting down to writing code. Hopefully they'll be of use to others too. :)

    Cheers
     
    Last edited: Nov 4, 2013
  14. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    Rule 1, 2, and 3 I all see as key parts of the Single Responsibility Principle - a grouping of code is focused on implementing a single responsibility. To that end you will need the container, the data, and the accessors to make that happen (I am speaking more about methods, and less about properties, even though in C# properties are methods under the hood.)

    Rule 1 is a tricky beast. While as much state and behavior as possible should be contained in the class, be aware that not all coupling is bad coupling! Controlling the coupling is what matters - coupling that is logical and clear can be OK. Unnecessary coupling of objects should be removed with extreme prejudice.

    I like Rule 2, though - that seems to be a really good guideline for determining A) what operations should be methods, and B) how to name said methods. That and the five-year-old test could help usher in a new age of readability for my code!

    Rule 3 hopefully is taught in every object-oriented programming class. Objects are things, which imply having a bundle of states and behaviors. For those without formal training, though, I think it's good to point out.

    Rule 4 sometimes helps and sometimes hurts.

    I'm less a fan of Interfaces - which merely specify accessor contracts - and more of base classes, which provide for a contract of both state and behavior. Often, I find myself requiring some default state and default behaviors that a more specialized object takes and expands upon, particularly in the AI work I've done on my most recent project.

    I find interfaces more useful when implementing things like Unit Tests, where I may want to 'mock' part of my framework such that a predetermined resultant state is returned for the fictional object. I find base classes more useful when dealing with an object that is an extension of another object, possibly with altered behavior.
     
  15. Brian@Artific

    Brian@Artific

    Joined:
    Jun 25, 2013
    Posts:
    35
    This is exactly why I love interfaces - they encourage decoupling of states.
     
  16. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Base classes are super useful for some things, of which AI is definitely one, but I'm a bigger fan of interfaces in general since they're so much more flexible and don't come with the baggage. You can only have one base class, but you can have multiple interfaces. Where I do use class hierarchies I do my best to keep them as shallow as possible.

    I find HarvesteR's Rule 1 pretty trivial to achieve with well designed event-based code. Objects which make any kind of change which other objects may care about are obligated to fire a message detailing the change, and anything that cares about any change made by others is obligated to listen for and react to the messages appropriately. Assignment of responsibility is clear, and the events can essentially be used to temporarily extend the scope of any data required for an appropriate resolution. It took me a bit of practice to figure out the best event patterns, but I'm now good enough at it that there is no inter-scope coupling except through the event system and my latest game has no high-level "Manager" objects because it simply doesn't need them. (That makes development super fast and flexible, as there's no boilerplate/glue code getting between me and the ideas I want to implement. It also lends itself to nice clean code structures which guide your code and encourage a lot of the good practices mentioned in earlier posts. Also, code is super easy to read because event names tell you what circumstances they're called in, and nice to debug because the event parameters tell you the circumstances you're looking at.)
     
    Last edited: Nov 4, 2013
  17. HarvesteR

    HarvesteR

    Joined:
    May 22, 2009
    Posts:
    531
    There is a point, when we're talking about coding practices, where personal preference or project particularities become a major factor. For instance, I personally tend to change my mind a lot about things while I'm developing, so over time I developed a coding style that allows me to change my mind about stuff I'm not 100% set on, without having to rewrite too much (it even actually works sometimes ;) ).

    I also do a lot of 'fooling the system' to test things that aren't quite complete, so interfaces and such are very useful on cases like that. Plus, because of the way KSP supports plugin mods, we have to make as few assumptions about the code on the 'other side' as possible, so that also factors in.

    Depending on the way your brain functions, and the way your project is structured, there may be less roundabout ways of doing things compared to how I'd do them, but IMO, that's the point where 'good practice' blurs into personal/project-specific style.


    Cheers
     
  18. welby

    welby

    Joined:
    Mar 22, 2011
    Posts:
    549
    I have to comment my code,...with my spastic schedule, sometimes it may be a day or two or more before I can return to it, and I'll be like,..wth was I doing here?

    With each script, I get better at 'organizing' and really taking advantage of feeding methods.

    I'm at the point in my current project where my AI is too good and I can't even test it without gimping it on purpose. A week later and I forget how the heck it was doing it,even though I wrote it....heh.

    I used to want everything on one script too,..but have since, chosen clarity and organization with folders.

    If I go back to an old project, I am certain to forget how it was doing anything so I spend time adding debug.log lines and retrace my steps.
     
  19. dxcam1

    dxcam1

    Joined:
    Feb 6, 2012
    Posts:
    477
    I only comment out heavy mathematical operations, just enough so someone can piece together whats going on. I hate it when people comment out every line of their code when it's just a basic print or rotate. Also, I understand all the code I write, if I didn't I wouldn't write it. I recommend taking the time to make some 5min UML logic charts of all your complex code. It'll save you headaches.
     
    Last edited: Nov 5, 2013
  20. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Trust No One.
     
  21. User340

    User340

    Joined:
    Feb 28, 2007
    Posts:
    3,001
    +1 for UML charts
     
  22. welby

    welby

    Joined:
    Mar 22, 2011
    Posts:
    549
    ha,..yeah,..call me Taw-Jieh.

    Though I'd prolly assign this title more to our department Rigger, who locks us looney animators out of channels he don't want us "tweaking" :p
     
  23. im

    im

    Joined:
    Jan 17, 2013
    Posts:
    1,408
    as soon as the project opens...
     
  24. deram_scholzara

    deram_scholzara

    Joined:
    Aug 26, 2005
    Posts:
    1,043
    Not since my early days. Sometimes I still have to remind myself how an algorithm or some math code works, but in general it's just a matter of taking a quick look at a few things to remind myself how it fits together.

    As for the people who are complaining about the code of others, keep in mind that sometimes you're right, and sometimes you just suck at reading the coding styles of others because you want everybody to match your own.
     
  25. Yoska

    Yoska

    Joined:
    Nov 14, 2012
    Posts:
    188
    I wish somebody had told me these back when I was starting with Unity. Had to learn the hard way. Well, I probably wouldn't had listened/understood anyway. Although I'm still very lazy when it comes to commenting. My excuse is nobody else won't ever read my code so it's okey to be lazy. And my future self hates me for it.

    This thread reminds me of this: http://hg.icculus.org/icculus/lugaru/file/97b303e79826/Source/GameTick.cpp :cool:
     
  26. zombiegorilla

    zombiegorilla

    Moderator

    Joined:
    May 8, 2012
    Posts:
    9,051
    No only that, but it is not uncommon for the scope or nature of a product to evolve over time. All to often a "prototype" can become the product. The choices someone made when a project started may seem horrible when working with it years later. Those choices/pattern/style may have actually been a good fit at the time they were made.
     
  27. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    The framework can go stale too.

    In my current day job, my team is rewriting a bunch of old ASP.NET web apps. For those not blessed with the opportunity to work with this most beneficial of technology[/sarcasm], it's pretty much Windows Forms on the web. We're rewriting said apps in the more modern, architecturally more mature MVC 4 framework. Needless to say, unraveling some of the code merely to discover an old system's business rules is a pretty nasty chore.

    I'm fortunate in that much of my efforts are going towards rewriting said systems, such that our team can monitor each other and prevent each other from creating a whole new generation of WTF-code.
     
  28. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,619
    Yes, precisely. I've often been responsible for decisions that worked well at the time but became bottlenecks later when the nature of the project changed.

    Also, since shipping is the one feature that your project absolutely must have, sometimes you'll have to compromise elsewhere to make it happen.

    I used to often see other people's code and quickly jump to the conclusion that they didn't know what they were doing. I now see that thought pattern as a sign if inexperience, though, because having been on both sides of the above issues multiple times now I know for a fact that an experienced and highly skilled coder will sometimes write cruddy code through absolutely no fault of their own. And having done it myself I know exactly how un-fun it is, so that's now where my mind jumps when I see such code. Not "geez, this guy was a noob" but "ah crud, this clearly wasn't made under ideal circumstances".
     
    Last edited: Nov 6, 2013
  29. darkhog

    darkhog

    Joined:
    Dec 4, 2012
    Posts:
    2,218
    Dude, post some of code here: http://thedailywtf.com
     
  30. Krileon

    Krileon

    Joined:
    Oct 30, 2012
    Posts:
    642
    Nope, I've written over 300 generic and re-usable PlayMaker actions. I then use FSMs for my entire game. Seeing what is going on is extremely easy to do. Most of the actions will never change or just need to be updated for the asset they were written for, but they're clear cut easy to understand. I'm a web developer by profession so I'm used to commenting, but I don't for my PlayMaker actions as they're just too simple to understand. I'm also in the habbit of using camel case with self explanatory function and variable names (I'm not afraid of a long variable name to describe what it does; don't use $a1, $a2, etc.. nonsense!).
     
    Last edited: Nov 8, 2013
  31. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    I'm very familiar with that site. :)

    But, I'd really rather not. Actually at my previous job, I'd submitted about three code WTFs, but they never made an article. Among those was an anti-class, an irregular expression, and some of the most obscure variable naming I've yet seen. ASP.NET I consider an auto-WTF, but it's not nearly as WTF as my previous job. That, and these guys are trying to modernize and I'm more than willing to help them, which means not making fun of them so much.

    Hopefully my opinion stays this way for time to come.
     
  32. ClockworkWolf

    ClockworkWolf

    Joined:
    Sep 12, 2013
    Posts:
    55
    lol tell me about it. I recently got to a good stage of my code, and decided to spend the time away from the code to find artists to create the in game assets; develop the "story" etc. Going back in last week, I was like... did I code this?! I have lots of classes that are very tightly coupled, and I think that's my big failing there.

    I'm not sure how to deal with this and am reading up on different design strategies, very likely going to refactor the code to something that is more suitable for long term updates.
     
  33. AndrewGrayGames

    AndrewGrayGames

    Joined:
    Nov 19, 2009
    Posts:
    3,821
    Again: not all coupling is bad coupling. There are times when Part A needs to know that Part B is out there. However, Part A's state and behaviors ideally will depend on Part B as little as possible. However, unnecessary coupling is unnecessary, and thus bad.

    Case in point: In my current project, I recently implemented a Death script. When the player's HP reaches zero, their game object is destroyed, and a death effect appears. This causes some side effects, too, like the Sidescrolling Camera I've implemented (which, tracks the Player game object) to all of a sudden have a stale reference.

    Where I started was simply having the Camera actively look for the exception. If it was found, a public bool was set, which the Death Script was looking for, so it knew to reload the scene. That merely solved the problem of reloading on death, but there's problems, like the fact that that level of coupling isn't necessary.

    For instance, it was more efficient just to do a FindObjectWithTag("Player") to acquire the player, and then to periodically test for the presence of the Exception I was seeing. It reduced an outward-facing dependency.

    Probably the next thing would be, instead of looking for a nasty, expensive exception, continuing with the same Observer pattern my GUI uses, and actually implementing a Death state for the player (it would also circumvent a Destroy() call, which causes some minor GC problems in the current Mono framework).

    I never said, "I never write WTF code." Sometimes WTF code works, and then you have to figure out how to take the WTF out of it. TODO comments are usually good for doing that part.
     
  34. Prodev101

    Prodev101

    Joined:
    Aug 6, 2010
    Posts:
    49
    Your all spoilt...

    I tried to look at some of my old code from my game done 20 years ago.. Written in 100% z80 with self modifying, bank switching during execution (it's a quick way to branch) etc etc... My head was in bits, my I wish I was 18 again..lol


    And to be serious I never use any real dependency now.. In the case from post above I would just fire a system wide message telling the game that the player was dead. Any code that wanted to know about this would register a callback..
     
    Last edited: Nov 11, 2013