Monday, January 26, 2015

Code, Dead Code and Undead Code

So my job recently has been helping to build a "New Rendering Engine" for this awesome piece of software now known as Codename Project Spartan (read about it on the IE Blog or in a more editorialized form on Smashing Magazine). Even if you don't read the articles, and you just scan the pictures, you'll see this awesome concept. A component called MSHTML with lots of things that people like, compatibility (yay my old stuff still works), legacy support (yep, stuff still works), and Intranets (where most of the worlds data is actually stored).

But then, like magic, we fly into the future (I'm assuming a slightly lighter color of blue means THE FUTURE) and we get some new things like interoperability (Ermagod, I wrote my site in one browser and it worked in another one, did I win the Internets?) and a single default engine (framework authors rejoice, no more multiple document mode IE).

Working on this cool new thing has enlightened me (or rather reinforced in me) a sense of how costly and interesting various types of code can be. While I can't go into details on the "New Rendering Engine" I can talk a bit about my observations while doing some refactoring and the various code smells that I came across along the way.

Code

Both of the Microsoft web platforms have a lot of code. We measure it in the millions of lines (each). Code is generally useful and it enables all of your key end user scenarios. Most code is well tested giving you have high confidence that it works as it was designed to work. People use the features provided by the code and provide satisfactory feedback, maybe a few nitpicks here and there, but overall the customer is happy.

Code is what we all try to write. Its the piece of software engineering we are all proud of.

Dead Code

And then there is dead code. When you shine a bright enough light on your code base you can find it. It looks quite odd and sometimes it doesn't even compile. Maybe it is behind an #ifdef, maybe it is a source file left out of the build system, whatever it might be this code is truly dead. But dead code doesn't stop there. The linker, in its omniscience, can figure out whether or not something is being used and it can omit it. Primarily you'll find a lot of unused template expansions getting evicted, but sometimes you get the Golden Ticket of all dead code that leads to many unused functions and member variables.

The cost of dead code is not that high if you think about it. First, you pay the cost of compiling it to some extent. You pay the cost to search index it and people find it when they do searches. Maybe it confuses new people on the team. Sometimes you even find that people go editing that junk without realizing just how dead it is (actually I find that a lot, its fun to let them know too, just before you delete the whole block). If there are member variables then perhaps that is costly to your run-time, since you are allocating space for members that are never used. You may even zero them out or initialize them paying other costs. Still these are easy to find because the linker gives you some hints.

So I said the cost was not that high, but all of those things above, in millions of lines of code must waste some time? Absolutely. But when we talk about wastes of time we have to focus instead on the relative cost. There is a more expensive type of code than dead code.

Undead Code

I never had a strong term for it, but after seeing what it is capable of I think it deserves to be placed in
the ranks of all the other undead horrors in the world. Undead code is bits of code lurking in your source that don't contribute to the positive experience of your end users. Code you've written, but provided no logical way to access. The compiler happily keeps it because it is so entangled in other conditions that it can't be provably removed from the code. Later someone comes along and from 30 calls away, passes the right parameter combinations and causes your code to raise itself from the dead and either crash and burn (in the best case) or provide years of buggy experiences to your users (in the worst case).

The costs of undead code are leaps and bounds beyond dead code. Why? Well, dead code has a big foam finger pointing at it if you are willing to look. So you can do something about it. The dead code might already be ifdef'ed out and so you aren't paying the price of compiling it or even fixing build breaks as the code around it changes.

Undead code harbors far more dead data members. Often times entire classes, v-tables, virtual calls, and lots of other really expensive stuff is being held ransom by a few undead lines. Those lines are also insanely hard to remove. You have to prove that the conditions of their existence can't be met by the other code. Sometimes undead code creates a nice circular reference and you have to chase through many layers to find the loop and eventually take a snip somewhere that lets you remove the entire thing.

I don't like to say that code has bugs. It really doesn't. It has capabilities and we really don't like all of them. Some of them are quite undesirable especially the crashing ones. So often this undead code creates a capability and depending on how long that code has been out there, sometimes people even start depending on those capabilities. Let's go so far as to say that some code is so complex that it can evidence emergent behavior, mostly due to this concept of undead code.

Bring on the Flame Throwers

As with all undead things, you want to burn them with fire. So break out your flame throwers and start making the round.

Probably the best flame thrower you'll find is code coverage. Thankfully VS has always supplied us with an excellent set of code instrumentation tools for figuring out our code coverage. You fire up your app, run it under the scenario, and then see if your code even gets a chance to run. Just like all other forms of exercise, exercising your code will help trim the fat and find better ways of dealing with error conditions or force you to think about how to make your code more testable. Strangely it also makes you write more tests if you ever want the numbers to go up. If there were ever a cure for turning undead code into deleted code it would be called code coverage.

Remember that dead code from before. Well, undead code likes to hide around dead code to mask its own stench. My favorite so far was an entire series of code behind an existence check for a variable. Basically something like this:
if (_pSuperCoolThing != nullptr) { ... Do tons of cool stuff ... }
Now that looks pretty legit. I mean, super cool thing, do tons of cool stuff. So I asked the linker to tell me about dead code and what did I find. I found that the function EnsureSuperCoolThing was dead. Wait a minute? Really? Oh BTW, CSuperCoolThing's constructor and destructor were also being thrown out.... Oh no, super cool thing, what are you doing, why are you not enabling my tons of cool stuff?

So once I realized the connection, you can imagine that I threw a party, revived everything and talked about how I spent hours writing a cool new feature... No, of course not. That tons of cool stuff had no tests, it hadn't run in many years, yes YEARS, and was not something I wanted to stamp my name on. I removed it. Dead code led to the removal of a bunch of undead code. Total number of edits to the code since being dead you might ask? Well, 4. Ouch, but its okay, because this also tells you how important it is to regularly run dead code analysis on your code and really clean it up. Make the time to do it.

There are also a few less technical/manual methods for finding undead code. A great one that a lot of companies use, especially in the web space, is simple telemetry. If you are logging your API, page, widget or component usage, then you can make data driven decisions on when to remove a potentially costly module. I mean, if nobody is using it, then other customers are paying the cost in disk footprint and unused capabilities. If there is a security hole or other type of bug, then it might even be a customer experience detractor. the only righteous thing to do is remove the code.

So if you, like me, hate dealing with unused, untested, undead code, then hopefully this gives you some ideas or ammunition to unleash the flame throwers in your code base. If you do, make sure you put in processes to keep the undead hordes at bay and decrease the likelihood of new infections through proper code coverage and testing.

4 comments:

  1. Something frightens me... Devs on IE team don't test their changes?!

    How can someone make some changes to a piece of code and not check if the change has the intended effect, or any effect at all? I mean if you edit dead code there is no observable outcome, so that doesn't say anything good about the dev's unit tests.

    ReplyDelete
    Replies
    1. Devs on the IE definitely test their changes. However, our code consists of millions of lines of dedicated code in the binary, and another couple of million lines in shared libraries. Getting direct execution of all of your changes lines with the tests that you can run in a reasonable amount of time isn't always possible.

      Let me give some examples. We have enough tests on IE to run on a server farm for a day, which in turn can take several days to process the failures and handled sporadic failure. This is what you might call a huge legacy. For our DRTs to run in just a single flavor (x86 chk) takes 2 hours of machine time (which can be split over multiple VMs for efficiency). That doesn't count, free, 64-bit, arm, etc... All combinations are simply not possible.

      Also, what kind of changes are being made. Microsoft has a great set of tools, many of which other companies don't have. For instance, we have a security language called SAL (which is in version 2). While you can use this in Visual Studio today, few projects do. We, however, use it extensively across our entire code base. So some updates might just be to SAL, and in the process they may tweak the code. An example here might be removing a null check because the SAL says __in or _In_. .Later you find that someone, somewhere still does pass a nullptr and so you now have a crash on your hands.

      Delete
  2. How About "WebBrowser Control" developers? I'm wonder to know about interoperability, APIs, etc. Webbrowser-control will support spartan engine? Where I can read more about?

    ReplyDelete
    Replies
    1. There is a legacy engine, mshtml.dll, and the new engine, edgehtml.dll. The exact configurations for those components will be made public during larger announcements such as the BUILD conference. Once those decisions are made public, then I would be able to talk more about them. Until then I won't be able to fully answer your questions here.

      Delete