Sunday, April 6, 2008

White Castle Programming: A Most Dangerous Game

Something that I deal with at work seemingly all the time is White Castle Programming. If you've never heard the term or don't know what it is, White Castle Programming is when one makes dubious assumptions as to the behavior of the client while writing one's code. This is code that will be interacted with in some way by a client. It could be a code library that will be accessed by client code, it could be a middle-tier layer joining two disparate layers, or it could be UI code that will be interacted with by a user.


Examples are in order. Let's say that you have a textbox on an ASPX Webform or a Winform and this textbox will be used to capture a dollar amount. If you assume that the user will never enter anything into this textbox other than a dollar amount, and you do not write any sort of validation code against that textbox, that is White Castle Programming. Or, let's say you're designing a code library and you give a class a public property that is some sort of collection, such as List, Dictionary, or whatever. If you assume that another developer writing client code against your code library will never use that collection in a way that you did not intend, that is White Castle Programming. In both cases, you are assuming that a user, be it an end user or another developer, will never do anything with your code that you did not intend. The logic in this thinking is something along the lines of, “No user would ever enter a non-dollar-amount string in a textbox that is clearly labeled for a dollar amount,” or, “It would take too much time for me to make this public collection property a private field and write access methods to it, and no developer would ever do anything stupid with the collection anyway.” The basis of these arguments is that it's inefficient to write code against outlier, statistically unlikely use cases and scenarios.


First, let me address the concept of YARGNI and how it relates to White Castle Programming. YARGNI, an acronym that stands for You Aren't Really Gonna Need It, is a concept that posits that a developer shouldn't write code that addresses a particular concern until there's a demonstrable need for the code. I am all for YARGNI and I try to practice it in my everyday programming; however, YARGNI can be taken too far and venture into the realm of irresponsibility. You wouldn't wait until after a user entered “DROP TABLE” into one of your textboxes before you began scrubbing your user input for SQL injection attacks, would you? If so, then I suggest you update your resume. YARGNI, however, would tell you to wait until your User table had been completely wiped out before writing such protective code, and in this way, YARGNI actually contributed to some White Castle Programming.


As you can see, White Castle Programming doesn't make a whole lot of sense. You're making very generous assumptions as to the behavior of any user of your code. However, you probably see examples of White Castle Programming in your projects every single day. Hell, it's something I see at work every single day. And I hate it. I despise White Castle Programming with every fiber of my being. It goes against everything I've been taught and believe about good, defensive programming practices. How on earth can a developer assume any particular behavior from the user?


So how do you fight White Castle Programming? Conceptually, you fight it with good, defensive programming practices. You write code that protects itself from the user. In the case of the dollar-amount textbox, you validate the user's input with a regular expression. For the code library with the public collection property, you take the time to do the job right, make that public collection a private field, and write access methods for it. Yes, both of those solutions require more time and add a little to your coding complexity. The payoff, however, is that you have greatly simplified the user's experience with your code. When an end user types “3.a6” in your shiny new textbox, now you can give the user a meaningful error message that let's him know that he mistyped the information. When a developer writes client code against your shiny new access methods to your private collection field, he'll know exactly what actions can be performed on that collection because you've provided access methods that are the client code's only interface with that private field.


When you program defensively, you greatly narrow the scope of the user's ability to interact with your code. You narrowly define exactly what actions the user may perform, and you create meaningful indications when the user does something that they shouldn't. Programming defensively makes your code structurally sound and ensures that the user can't do something that you did not intend. When you the developer defines what actions the user may perform on your code, you empower yourself to give the user the best experience possible, and isn't that what programming is all about?


Defensive programming is the conceptual method of fighting White Castle Programming. What is the practical method? What if, during a code review, your boss asks you why you added some extra validation code, and when you tell him that you were just defending against a certain use case, he fusses at you for wasting time programming against a statistical unlikelihood? The best thing to do is to actually show your boss the problem and demonstrate it for him. Most bosses, once they see that a problem is possible and that it could very well cause problems with the system, will be glad to let you leave the code in and will respect you for your foresight.


If you think about it, programming defensively is more efficient than waiting for the problem to occur. When you narrow the scope of the user's interaction with your code, by default, you also narrow the range of errors that can be produced as a result of users' interactions with your code. Problems become easier to track down and solve.


White Castle Programming, on the other hand, leaves the user open to perform any number of strange and unexpected interactions on your code. When you assume that the user will only interact with your code in an intelligent way, you widened the range of errors that the user can cause. The wider the range of errors, the harder they are to track down. You'll have to spend more time stepping through your code, trying to reproduce the user's problem. If you don't have overly descriptive error-logging in place, then it will be even harder as you try to figure out exactly what values the users' variables had, exactly what line of code they bombed out on, and so forth.


Of course, a good developer can't go willy-nilly with defensive programming. YARGNI teaches us that you don't want to attempt to defend against every possible use case. So how do you know which exceptions to your code you should program against beforehand, and which you should wait until they occur? Well, that intuition comes from experience. If in the past, you've seen that clients tend to perform one certain error that could possibly affect your code, then you should program against it. If it's something that happens often or could cause a serious problem to the system, you should program against it. Anything else, you should wait. I know that's a somewhat vague answer, but if you keep your eyes peeled, you'll eventually pick up that intuition.


I'd like to end this blog post with one of my faves: Telling about a real-world experience that totally demonstrates my point. Names and circumstances have been changed so that no one will know who I'm talking about and therefore get offended, though I never mean any offense with these real-world examples.


The boss comes up with some new functionality that will add value to one of our .NET Windows apps. After a few meetings and some brainstorming, fully fleshed out specs are given to me for implementation. There is no code in place, just database tables, so I get to design all of this from the ground up. I write all the stored procedures, design the business classes, and lay out the screens to spec. However, I program defensively. All of my Windows forms have extensive user-input validation. Hell, I even wrote some custom validators that validate against some third-party Windows controls that we use. I surround all my code that touches the database with try...catch blocks. I use various value types' TryParse() methods instead of Parse() methods. I wrap commonly used variables in form-level private properties that can perform their own logic and return certain values under certain cases. Lots of little things like that that add up to good defensive programming.


After I've written the code and designed the screens, I begin testing. On one particular screen, I accidentally mistype some figures and find that it's possible to enter data that would cause serious problems to the system. In particular, I find an error that could incorrectly bill one or more of our accounts. I see the problem, take ten minutes to add the logic to defend against it and BAM! problem solved.


Later in the afternoon, I go to a coworker's office and tell him of the serious error I'd found and fixed. His brow furrows and lips tighten. He then proceeds to tell me that he's not sure how valuable it was to program against that error because he doesn't think it's likely that the user would make that same mistake. I argued that it doesn't matter how statistically likely the error may be, I had already demonstrated that it could occur, and when it does, we would have not only invalid data, but invalid billing data. My coworker agreed that it was probably a good idea to check for this error but continued to say that he didn't think it was likely to happen. Never mind that I, who wrote the code myself and was intimately familiar with how it worked, had demonstrated that the error was possible, and had accidentally performed it myself. Let's see, that would be a statistical likelihood of, um, 100 fucking percent, right? However, my coworker did agree that the error should be defended against, so I went back to my office and continued on my merry little path of programming defensively (not that I would have done differently had my coworker disagree with me).


And you know what happened? I finished testing the new functionality and deployed it. A while later, after this code had been in use for two weeks, my boss, who never compliments anyone on anything ever, told me that I had done a “very good job” with that functionality. It was working right out of the box and was not generating errors or invalid data. We made one small set of changes based on user feedback and that was it. BAM, muthafucka! My code, bitch! And what was the reason of my success? I was a humble programmer and programmed defensively, never falling into that White Castle Programmer mentality of assuming that users of my code would magically know what and what not to do with it.

2 comments:

Chris Benard said...

Too long. Did not read.

Michail said...

Short enough. Read it all.