tag:blogger.com,1999:blog-43545791033742061532023-11-15T07:11:51.186-08:00Preliminary AccountDaumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.comBlogger15125tag:blogger.com,1999:blog-4354579103374206153.post-69147121911516310472010-06-08T18:22:00.000-07:002010-06-08T18:27:35.860-07:00Genius or Mad Man?I presented an ambitious proposal to a couple of engineers more experienced in the space, and whilst everything they said and asked about was measured and reasoned, the look they had on their faces was as if they were wondering whether I was sane. On reflection, it is a look that I have likely given to others many times in the past, whenever I am thinking "if you manage to achieve what you propose, I will be suitably impressed by your genius. If not, I will merely be right."Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-6348418768118251522010-04-25T23:10:00.000-07:002010-04-25T23:52:07.680-07:00Unit TestsEarly on in my career I used to think that there existed code that was "too complicated" to unit test, especially code doing multithreading. A few years experience on this type of code base taught me that "too complicated to unit test" is just another way of saying "unmaintainable". What I really needed to do was to change the idioms I was using so I could write something unit testable [1].<br /><br />The flip side to that mistake was I argued that since we couldn't unit test the most complicated parts, we shouldn't unit test the simple code either. The mistake here was the assumption was that bugs were much more likely to appear in the complex code then the simple code. Whereas in reality, because the complex code was complex, it was subject to a lot more ad hoc and integration testing than the simple code, which just "obviously" worked. The key thing is that most bugs turn out to be trivial (e.g. cutting and pasting a loop and not changing the loop variables in all cases), but code is so dense with information it is often very hard to spot these mistakes by eye, so with their path untested they all too easily make it to production and cause some behavioral bug in some edge case, and after days go by before it is observed, then you have spent hours tracking it down to a one-line bug, you will wonder how the entire software world keeps itself together.<br /><br />That being said, you have to be careful with unit tests. They can add a lot of almost deadweight code to your base, making trivial changes to functionality take several hours just in refactoring unit tests. Now, modern IDEs with refactoring tools can help a lot here [2]. But certain of these problems are not refactorable.<br /><br />The most important thing is not to treat test code as a second class citizen. In terms of quality it must be treated the same as production code So for example, since we all know that copying and pasting a code block 10 times is bad in production code, then it is just as bad, for all the same reasons, in a test, or across a set of tests. What this means in practice is you end up with lots of little methods to construct helper objects to put into tests, e.g. replacing N replicas of this[3]:<br /><br /><span style="font-family:courier new;">MyObjectUnderTest obj = new MyObjectUnderTest();</span><br /><span style="font-family:courier new;">obj.setFoo("foo");</span><br /><span style="font-family:courier new;">obj.setBar(</span><span style="font-family:courier new;">"bar"</span><span style="font-family:courier new;">);</span><br /><span style="font-family:courier new;">obj.setBaz(</span><span style="font-family:courier new;">"baz"</span><span style="font-family:courier new;">);</span><br /><span style="font-family:courier new;">doTest(obj);</span><br /><br />With N of this:<br /><br /><span style="font-family:courier new;">doTest(</span><span style="font-family:courier new;">createObjectUnderTest("foo", "bar", "baz"));</span><br /><span style="font-family:courier new;"></span><br />A second important aspect is to test just 1 object at a time - don't couple objects under test. Doing 2 (or more) at once often seems like a time saver ("I'm testing three objects with one test, yay productivity!"), but as soon as you want to refactor one of the objects (e.g. to use it in another place) you will be regretting your choice as you have to understand the complex interactions the test was testing, and then rewrite them. <a href="http://easymock.org/">Mocking frameworks</a> and judicious use of Abstract Classes/Interfaces can be your friend here.<br /><br />As to when to write the tests, TDD is fine if it works for you. I find it most useful when trying to write classes where I want to get the API right. But after all I am generally writing a applications as opposed to a library, and so any individual class's interface is not that important, and so I use it rarely.<br /><br />One thing I do force myself to do is write code in units, and write the test straight after each unit. The thing I noticed is that if I delay writing unit tests until after all the units are working together end to end, then because after all the system "already works" my subconscious enthusiasm for writing unit tests falls markedly, and their quality and coverage fall likewise. Whereas if I write the unit tests just after each unit, it's part of "getting everything to work", and so I am willing to put the effort into doing it well.<br /><br />In the end though, I have found the greatest value of unit tests is that they give me direct feedback as to just how high my error rate is in writing code. I have lost count of how many times I have written the code, written the test, thought "this is sure to work", and in fact had several quite serious bugs that need to be fixed. It is forever a humbling experience, but humility is a good value to have when working on large, complex software systems in a corporate "we need it yesterday" environment.<br /><br />[1] In particular, I was writing mulithreaded stuff on the Win32 API and trying to get by with Semaphores and Events, and interlocked stuff for cross thread synchronization. All very difficult to unit test. When I finally switched to Posix Condition Variables, everything was much easier. The other thing I learned here was as much as possible objects should not own threads - rather they should be driven by threads. Thus you just need to unit test state transitions of method calls. Now, this is still not checking for race conditions and deadlocks - for that the only solution I have found is good up front design with well defined semantics.<br /><br />[2] It's one of the many reasons that dinosaurs still using Emacs for writing Java are doing a bad job - they either aren't writing enough unit tests or not doing enough refactoring.<br /><br />[3] Of course a better language with named parameters would make this all less monotonous. But like most people in the real world I am stuck with Java or C++ on any project large enough that it needs type safety.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-43842415084394780582010-04-23T17:22:00.000-07:002010-04-23T17:24:14.058-07:00PruningOne under appreciated skill that you need as a software engineer is the ability to quickly and accurately prune your solution space. For any problem you will have a range of possible options on how to solve it, and most of them will be bad. There are three tricks I have found useful to being successful:<br /><ol><li>In the initial evaluation step, knowing and using the right heuristics to choose between the options. The ideal heuristic is both low cost in the sense you don't spend too long evaluating each option, but also accurate in getting the most probable success.</li><li>Once an option is chosen and implementation is underway, be continually critically evaluating the new information gained about the option's viability, and then intuiting when the downsides are getting too high relative to the next alternative.</li><li>Once that point is reached, being willing to put aside what you have done to try the next alternative. This is often hard as a) it seems like you have wasted your time (i.e. the <a href="http://en.wikipedia.org/wiki/Sunk_costs">sunk cost fallacy</a>), and b) it is sometimes intellectually dissatisfying to put aside a problem unsolved.</li></ol>Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-61692473368641860022010-04-05T21:00:00.000-07:002010-04-05T21:02:29.836-07:00Crap All The Way DownYou have a project with requirements W,X,Y and Z. You find an existing bit of software, be it tool, library or even source, that looks like it will do a lot of what you want. This is how it proceeds:<br /><br />Start: "Wow this bit of software looks good, it should do exactly what I want."<br />Proceeds To: "Hmm, it handles these edge cases a little bit funny, let me investigate more."<br />Proceeds To: "What a pile of crap, why did they handle such important cases in such a shitty, half assed way. This will never work. I'm gonna look at something else."<br />Proceeds To: "I can't believe it. All the other options are just as crappy. I'm gonna start from scratch, how hard can it be?"<br />Proceeds To: "Wow, this underlying bit of software is just an incomprehensible ball of crap, this would take years."<br />Proceeds To: "Oh well, if I just cut out requirements W and X, and just do crappy half way implementations for Y and Z, then at least I will have something."<br /><br />Then someone else comes along, and chooses to use your software for their project.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-74020823857173267602009-10-17T18:42:00.000-07:002009-10-17T18:46:37.485-07:00Marginal ThinkingOne of the most most powerful ideas I have taken from economics is thinking at the margin. To quote one of <a href="http://gregmankiw.blogspot.com/">Greg Mankiw</a>'s principles of economics, "<a href="http://books.google.com/books?id=oRgQ2goeFzwC&pg=PT38&lpg=PT38&dq=principle+of+economics+makiw+margin&source=bl&ots=4p9N_8jaV5&sig=iOPz54IwAaD906oralolbTW_kU8&hl=en&ei=zlLaSsmMA4_EsQPRobmJBg&sa=X&oi=book_result&ct=result&resnum=3&ved=0CBQQ6AEwAg#v=onepage&q=&f=false">Rational People Think at the Margin</a>". I think like Mankiw occasionally tends toward, this quote is ironically mischievous, and to some extent the mischievousness undermines the profundity of the message. Mankiw is not backing a horse in the old debate of whether "people are rational", or "markets are rational" - the "rational thinking" applies to the analyst, not the actors. So what he is basically saying is "In analyzing a complex system of actors or events in light of possible changes, a rational analyst thinks in terms of how actors/events at the margins are affected by the changes, rather than trying to reason about the affect on all of them, or even an average one".<br /><br />A good example is if you ask someone "Do mandatory seat belts cause there to be less fatal road accidents?". The immediate common response is, "of course, since fatal accidents can become merely injurious". This is true, but not complete, the followup question is "Do you suppose, by engendering a feeling of safety, that seat belts could cause more dangerous driving, and so more accidents". Here people think about their own driving, and whether it is influenced by their wearing of the seat belt, and conclude it isn't and so answer "No". But it is here that their thinking is less than rational. Yes, most people are good, safe, average drivers in their normal range of experience, and a fairly small accident rate across the population reflects this. But it is the margin where things matter; is there marginal drivers who might feel a little safer with their seat belt on, and so might drive a little closer to the edge? It seems the answer must be yes. So on balance then are mandatory seatbelts good or bad? Well from analysis of actual before/after measurement when laws have been enacted, results <a href="http://www.safespeed.org.uk/forum/viewtopic.php?t=4116">are</a> <a href="http://ideas.repec.org/p/nbr/nberwo/13408.html">mixed</a>.<br /><br />This is a fairly unintuitive result, and economists like Steven Landsburg And Steven Levitt <a href="http://www.amazon.com/dp/0060731338">have</a> <a href="http://www.amazon.com/dp/0060889578">managed</a> to <a href="http://www.amazon.com/dp/1416532218">fill</a> <a href="http://www.amazon.com/dp/0029177766">books</a> by applying similar reasoning to a range of similar systems, to get similar unintuitive outcomes. The important point is they follow their reasonings with actual experiments to test the reasoning, and it is this that carries the weight of the argument, and allows it to win over the default, naive, intuitive analysis.<br /><br />Which brings me back to the "ironically mischievous" aspect of Mankiw's principle; that since results from thinking at the margin seldom match with peoples intuition, they are extremely difficult to convince people of over "average case" type thinking. Or in other words, most people are just not rational.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-24077365200935290672009-07-03T00:22:00.000-07:002009-07-03T00:23:07.577-07:00Lessons of WaterfallNo thinking person did Waterfall the way it was described in the literature written by the "Big Process" gatekeepers. Rather, they did what was appropriate to the problem at hand and then did the minimal amount of Waterfall waggle dancing needed to keep the gatekeepers off their back. Unfortunately this had the side affect of making both the gatekeepers and the management hierarchy think that their process was working, and the whole Big Process industry managed to perpetuate itself for well over 20 years. Until Extreme Programming came along, made a lot of noise about being different, and then through their "one right way" dogma managed to continue the charade, just with a different set of gatekeepers cashing in.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-20264442422549914082009-07-02T09:33:00.000-07:002009-07-02T09:35:00.514-07:00TDD as Future Self PaternalismI have a lot of trouble stopping myself snacking when I am concentrating on a problem. In the moment when my brain is distracted I lack the mental awareness to control my behavior. I can be back and forth to the cupboard and halfway through a pack of chips before it even hits me what I'm doing. For a while, I struggled with this and struggled with my weight, but eventually I hit upon an ingenuously simple solution: just stop buying snacks. Then in the moment of thought, they won't be around to be eaten.<br /><br />I am not a Test Driven Development (TDD) dogmatist, my coverage only averages about 60%, and I rarely write tests before code. However, I do force myself to write code in units, and write the test straight after each unit. The thing I noticed is that if I delay writing unit tests until after all the units are working together end to end, then because after all the system "already works" my subconscious enthusiasm for writing unit tests falls markedly, and their quality and coverage fall likewise. Whereas if I write the unit tests just after each unit, it's part of "getting everything to work", and so I am willing to put the effort into doing it well.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-47105656425305730872009-06-27T10:27:00.001-07:002009-06-27T10:30:55.451-07:00Ruby Line NoiseRuby is a nice language but it shares many of the failings of Perl. One of these is with all the symbols it often reads like line noise to someone not fully familiar with the language. This is bad because for a script in the enterprise, pretty much every reader bar the author isn't going to be that familiar with the language. There's just too many choices of language and so the knowledge base is very fragmented.<br /><br />The absolute worse decision along these lines was the symbolic operator overloading done in the container classes. Now, symbolic operating overloading may be fine as a general language feature to allow for well targeted use cases. You dont really want to be doing "new BigDecimal(5,6).plus(new BigDecimal(1,1))". But if you put shit like "|=" and "<<" in your basic Array class, it only encourages the dimwitted masses who follow to do similar. And because they are dimwitted, they won't pay enough attention to getting the semantics consistent with expectations, they probably won't even write a comment or a unit test, and you end up in a unreadable, unmaintainable mess. But hey, at least its a concise mess, right?<br /><br />The simple rule here is: favor general readability over conciseness understood only by experts.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com1tag:blogger.com,1999:blog-4354579103374206153.post-42281725387809141302009-06-26T21:08:00.000-07:002009-06-26T21:09:58.962-07:00Pro Static Typing Argument 1When you are writing code you are making what seems like one hundred decisions simultaneously on a whole variety of levels, from what algorithm to use to what data structures to use all the way down to what to name variables to make them consistent with the rest of the code base. I think even the best engineers can only keep a handful of these in the front of their brain at any time as "rational decisions", the rest are just taken care of by intuition and habit. Most of the time this works fine, but every so often the habitual or short circuited intuition decision is a bad one. Thus an important criteria of which tools and languages you use are ones that have very slow cost in exposing these errors. I think static typing is one of these tools.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-51073275683880887522009-06-24T08:20:00.000-07:002009-06-24T08:27:28.445-07:00Performance Reviews are not about Reviewing PerformanceAnnual performance reviews are not about reviewing, rewarding or appreciating every employees relative performance. This is impossible as basically every employee thinks they have performed relatively above average. Rather, they are about shaking out the marginal employees who would quit in the next year if they don't get some status recognition, deciding if the company really wants to keep those employees, and if so giving them a plum. Everyone else gets turnips.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-17284608783221319372009-06-23T09:27:00.000-07:002009-06-23T09:30:27.917-07:00Databases and RefactoringPerfectly normalized database schemas are an anathema to having well factored code in the long term. The problem is even a simple refactoring like "Rename Column" is a tremendous amount of operational risk, and simultaneous work in all dependent services. The more I have worked in cases like this, the more I favor storing any field which doesn't need to queried on as part of a JSON blob. These are structured, human readable, easily read from almost any type of client, and much easy to put in policies to do with versioning and deprecation without risking an outage.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-21512692012021903332009-06-22T09:25:00.000-07:002009-06-22T09:28:03.267-07:00Dysfunctional CommunicationIt is often overlooked that dysfunctional team communication can go two ways. Sure, you can communicate too little. But you can also communicate too much and get stuck in a quagmire trying to rank competing intuitions rather than getting things done. You need a "gray space of trust" where individuals can get their part done without needing to fully justify their intuitions.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-80049619031318467852009-06-21T16:49:00.001-07:002009-06-21T16:50:23.760-07:00How Code Is ProducedNaive programmers believe that when they program they are using their "logical proof" part of their brain - the code is a "proof" that can be verified by inspection. In reality they are using their "intuitive pattern solving" part - just building up an imperative map from input to expected output. This is why unit tests are so important. Code without tests is just a hypothesis with no experimental evidence.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-40132469552448286702009-06-21T13:45:00.000-07:002009-06-21T13:46:24.361-07:00Functional ProgrammingPeople generally think best in terms of recipes, the imperative. Machines reason best about declarations. Functional programming is based upon the notion that if you limit humans to express themselves to terms favored by machines, then better software will result. This seems misguided.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0tag:blogger.com,1999:blog-4354579103374206153.post-46199233979071900592009-06-21T13:43:00.000-07:002009-06-21T13:44:35.472-07:00Fragments Are The Only Form I TrustThe contents here are going to be short and pithy. No stories or anecdotes for comfortable consumption. Because they are short they are going to generalizations, and as such they are going to be something short of the truth. The hope is they contain some truth. They are certainly not nothing.Daumierhttp://www.blogger.com/profile/16376895984758615949noreply@blogger.com0