Modifying large codebases in dynamic and static languages

I’ve been wondering recently about dynamic languages, and static languages, and the relative benefits.

I’m struggling with this question because I write C#3 by day, and am learning python in the evenings. I’m only writing small python scripts at the moment and I’d like to write larger pieces, but I’m concerned about how easy it’ll be to make certain types of change.

For example. You’ve got 100,000 lines of code. You also have a logging function that’s looks like this;

void Log(string message)

And it’s called about 200 times in your code. You decide you need a severity; so you change the signature to

void Log(string message, LoggingSeverity severity) { .. }

Now, how long does it take to find all the calls to the Log() function that need to be updated? Under C#, about ten seconds. Once every call has been fixed, the code is almost certain to work correctly.

Consider, on the other hand, the python function

def log(message):

What happens if you change the signature to

def log(message, severity):

There is no way to tell where the log message is called. You’ve just introduced 200 bugs.

It’s made even worse by duck typing; maybe you have two loggers — a deployment logger which writes to a database, and a test logger which writes to stdout. You update the database logger so it has severity. Your tests continue to pass, but your deployed system will fail.

So it seems to me that static languages give you much more power to make changes to large codebases. I’d love to know if, and where, the mistakes are in my thinking.

Advertisements

8 thoughts on “Modifying large codebases in dynamic and static languages

  1. Gah – I know what you mean.

    Actionscript will helpfully call the new log function with a null parameter where you’ve missed one out, but if you accidently type a function and called lgo(msg) instead it will just ignore the call and not raise an error message or anything! That’s what you get for using a language that you can dynamically add functions to objects on the fly.

    In a similar vein – I like strongly typed languages.

    In other news – have you read “Working effectively with legacy code” by “Michael C Feathers”? It’s all about moving towards good testing practice in existing code (rather than all the books about good testing practice that start by assuming you’re writing a brand new project and don’t have any dirty code to start with); and a relatively entertaining read for a dry subject.

  2. have you read “Working effectively with legacy code” by “Michael C Feathers”?

    No, I’ve never seen it. I might have to search it out next time I’m in borders.

    I’ve seen something similar to the actionscript behaviour in Ruby — when you call a non-existing method, it actually calls another method called missing_method, which throws an exception. However, it can be overridden, which means you can specify a kind if ‘oops, sorry’ behaviour to get things back on track. Either brilliance or pure evil. Or both.

  3. I’ve been doing ruby full time for a few years and I have yet to run into a problem with this. First, a project wide search and replace (with confirmation on each replace) will do the trick. It’s a tad slower than with fancy ide’s, but it usually takes me less than 10 minutes and how often does this really happen?

    Second, the tests should catch the errors. You setup a bit of a perfect storm by saying that the tests use a different logger. This may get through the tests, but our staging server would catch it before it ever hit live since it emails me every time there is an error and a bad logger would error almost immediately.

    In the past few years I can recall 2 or 3 times that there has been a bug from dynamic code, none of it ever hit the live server, and it took maybe 1 day total time to hunt them down. Most of that time was one particular bug. How much time did we gain in productivity using ruby compared to what we lost with these bugs and the slower but infrequent refactorings? No one knows for sure, but my money is that it’s been a net plus for me.

  4. You could use a default parameter for severity

    def log(message, severity=”low”):

    or do a search in files for log(

  5. @jay

    Thanks for taking the time to reply!

    You setup a bit of a perfect storm by saying that the tests use a different logger.

    I was trying to get in the idea of mocks. Some objects rely on I/O; say, a logger, a database connection, or a file. When unit-testing something that calls it, you want to swap out the logger/connection/file for a mock object. Otherwise you’ll hammer your event log, database, or file system when you run your tests.

    So let’s say I’m now running a function which calls the logger; say MyDocumentedProcedure(). During testing, I always set the global Logger to a new MockLogger. Well, MyDocumentedProcedure() may work absolutely fine because MockLogger is set up correctly. However, if ProductionLogger hasn’t been updated, it’ll fail there. Of course, ProductionLogger should have it’s own set of tests, but there’s no guarantee that ProductionLogger’s tests aren’t just out-of-step with the new log(msg, sev) function.

    I think it’s a matter of how you want to program — I’d like these sorts of errors found for me by the computer. It’s feels natural, and I’m fluent with it. That makes me productive with this kind of system.

    @steve

    You could use a default parameter for severity

    The problem with this approach is that my requirements have changed. I’ve gone from the requirement ‘All messages are logged in the same way’ to, say, ‘messages have a severity of Alpha, Beta, or Release, and are logged differently based on the releaseType variable’. Much of this requirement is enforced throughout the code when I change the method signature to

    log(string message, LogSeverity severity)
    
  6. i understand, but in 3 years of full time usage of ruby, it’s never happened to me. Even if it happened it would be caught automatically on the staging box where I’d get an email as soon as the first exception is thrown. Also, if you do comprehensive testing it will include testing with a framework like selenium which would also catch it.

  7. @jay

    Thanks for the reply, Jay. I think I’m becoming convinced of your position. When I can next justify a decently-sized hobby project, I’ll try to do it in a dynamic language with lots of tests. I’ll probably do it in python.

    I suppose the key will be to make sure there’s 100% code coverage and enough permutations to be confident of the code. How do you make sure your tests are getting enough coverage?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s