Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Dynamic Languages are Unmaintainable (williamedwardscoder.tumblr.com)
49 points by mpweiher on July 1, 2013 | hide | past | favorite | 69 comments


One thing I've only come to appreciate during my professional development is that there are huge differences in the kinds of software people write and what's good in one scenario isn't necessarily good in another. A lot of people around here fall into the trap (myself included) of thinking that web programming is the entire universe of programming. That's not so, and the difference matters greatly.

If you're going to write a physics simulation, you're really going to benefit from some language features over others. I would argue that you especially benefit from static types because you control a lot of your stack and type checking can make your code very robust. On the other hand, if you're going to write a webapp, think about what you're doing: essentially slinging text around and taking random inpust from users, casting all of it to actual types, and shoving it into a data store (casting it again sometimes). What is the type system providing you in that scenario? Everything is cast at runtime somewhere.


> What is the type system providing you in that scenario?

Though the data flowing in and out of a web application is usually in string form, that doesn't mean that it needs to be treated as such in your application. You'll convert the string to a richer, more specific datatype and the static type system will help you correctly manipulate the data.

A nice blog post on the subject is [1] where the author explains how you can represent different kinds of strings (raw strings, SQL strings, etc.) using the type system.

[1] http://blog.moertel.com/posts/2006-10-18-a-type-based-soluti...


Thanks for that post, I think the point I was making was slightly different. I'm arguing that with web programming you lack the end to end control you might have in a closed system. You depend on all sorts of input from users, your data is stored in a database or nosql store that is outside your program's (and compiler's) complete control. It can change underneath you at any time. The argument that static typing can help you know that your code is correct is weakened in this environment. Internally you of course construct your code in a way that uses types and can verify that you are internally consistent. However, I'm saying that at a fundamental level, in a data-driven web app, the system as a whole is only marginally improved by this because your whole universe starts out with a bunch of typecast operations as the gateway to your codebase. These always are happening at runtime and are one "ALTER TABLE" (to pick just one example) away from failing spectacularly in production.

I'm not arguing one way or another that static typing is/is not good in this scenario. I'm just pointing out that the foundation of the system as a whole is not as solid as other programming scenarios where you have much more control.


I have done fair amount of numeric programming and I have not seen any significant benefits from type checking. For me, only these increase robustness:

1. Ability to minimize the amount of code you write. Code you don't write is code without bugs. this means either writing problem specific libraries or having them in the system (Matlab creates more robust code than writing it all by yourself)

2. ability to minimize boilerplate. This is where dynamic languages often win.

3. Ability to change code easily and test or run frequently (not necessarily unit testing). Rewriting usually makes code more robust. Time and money always runs out. Always.

The only constant law for selecting programming language for numeric programming is: IF YOU DON'T HAVE TO SPEND SIGNIFICANT TIME OPTIMIZING YOUR CODE AFTERWARDS, YOU ARE USING TOO LOW LEVEL LANGUAGE. If you don't have to optimize for speed, it means that you have been micro-optimizing your code everywhere without even noticing it. This is why C, C++ or Fortran should never be the main programming language you use. Use Python, Common Lisp, Matlab, Haskell, whatever and when you hit performance bottleneck, you write minimal amount of C or C++ code to get over it.


Having written a lot of scientific code in Python, I agree (partly).

The lack of static analysis is extremely irritating when you've been running a long old calculation for 20 minutes and it chokes on a misspelled variable name. You can often catch these with PyDev/eclipse, but not always.

I try to rewrite any code which isn't fast enough in C (or C++, using a few of its "features" as possible) and wrap it up as a Python module; this gives me the best of both worlds: the speed and type checking of C/C++, with the convenience of calling the code from a quick script or an IPython shell.

This approach is much easier for scientific code than for web apps, though, which do actually need dicts and string parsing.

[[One web app option might be to cython as much of your pure python code as possible (including cdef'ing many of your local variables) as this will dramatically help typo checking - I don't know if this is particularly scalable though.]]


> when you've been running a long old calculation for 20 minutes and it chokes on a misspelled variable name.

If you are writing C++, you can't hot edit and continue without re-compilation. With a scripting language and proper flow control, you can edit upon exception and resume.


I do often break into a REPL on a python exception; but I mostly use that to diagnose the problem and restart from scratch, rather than actually editing the code inside the REPL.

This is probably because I don't have the setup required to persist the changes back down to the source file, even when that source file is different from the one actually loaded (eg in my development folder rather than the installed site-packages version.)


> you can't hot edit and continue without re-compilation

But with recompilation you can. watcom allowed stepping back during debugging as long as the operation was possible to revert. From there patching up the code to include a new version of the function shouldn't be that hard. Not sure if anyone's actually done it, but it's certainly not impossible.

Ksplice works similar way if I'm not mistaken to do reboot-less kernel upgrades. (patching the functions part that is, not recovering the code that's already running them)


Ksplice is basically a binary (in ELF format) swap in kernel memory space.

What happens if your binary is faulty?


I use dynamically typed languages maybe 90% of the time, and completely agree. But here's the thing: for most projects - including ones in production - it doesn't matter because the app won't be large enough to not completely fit in a mental model.

If the app starts getting too large, then by all means rewrite it in a statically typed language. If you're getting to that point, a rewrite is probably warranted anyways; regardless of the language you choose, first versions are usually awkwardly structured and difficult to maintain anyways since features evolve faster than architecture.


I would also argue that you can get the benefits of a static type system with hardly any cost. Usually when people talk about dynamic vs static typing they imagine something like Ruby or Python vs Java, but there's no reason why you can't have a high-level, expressive language with static types.

All you need is a sophisticated type-system and smart type-inference that works everywhere (not just locally) and you'll be able to be just as productive as with today's popular dynamic languages but get all the benefits of static verification.


OCaml, F#, Haskell all come to mind. Regarding total type inference, I am not sure though that any of those can do completely away without some manual type annotations. I still like to declare type annotations (especially in Haskell, the syntax in ML is a bit too ugly) for top-level functions.


I can only speak about Haskell here, but how often do you need to provide a type annotation. I have never been in a situation (outside of the interactive prompt) where the compiler failed to infer the type. I do provide many type annotations for the sake of readability, but as far as I am aware the only effect these actually have on the code is providing additional restrictions on where a function can be used (that is to say, intentionally making the type signature less generic than it could be).


standard ml (and thus haskell) has total type inference. the problem in haskell at least are extensions which ruin some inference.



I'd add: one way of dealing with this is to have lots of loosely coupled small systems.

Easier said than done and sometimes not an option, but it's a strategy that can work with web app types stuff. The buzzword here's probably SOA.


The argument between dynamic and static languages has gone one for years and there's been no conclusive answer that one is better than the other.

People have written 500kloc C++ systems and they've written 500kloc of TCL.


Submitter here: Yes, and some of the claims are simply factually incorrect: the original work on refactoring OO programs was done in the context of Smalltalk, the code navigation we now have in IDEs was taken from Smalltalk (in fact, Eclipse started out as Visual Age Smalltalk) etc.

So I find the post "debatable"...


I have used the old refactoring browser in VisualWorks. It was cool for its time. I's pathetic compared to modern refactoring tools for static-typed languages.


(blog author)

Well I thought refactoring went all the way back to Forth.

I'm sure that with some language xyz it worked out very well for some project abc and that obviously disproves my whole point and you can move on...

I found writing large projects in Python - a language I was very familiar with, expert even - to become very difficult to extend aka maintain after a long time. My post should be seen in the bigger picture of the post I was following up about "Scaling My Server", and others I have written.


> Well I thought refactoring went all the way back to Forth.

The original refactoring browser was in Smalltalk. Forth programmers don't go about that type of thing the same way as other language since the goal is really to write good "words" that express your program. It is more building a better language than traditional refactoring.


Isn't "some language xyz worked out very well..." and "I found writing large projects in Python..." basically the same (anecdotal) argument?


Yes, but its a subjective post. If someone wants to say or blog "I use xyz and I can successfully maintain and extend large projects and it hasn't collapsed under a cognitive burden and too-specific unit testing" that'd be a cool counter data point.


Advocacy is cool now? Statistically you could come up with almost any methodology, sane or not, and find one project using it, unless you're going for outright cruel jokes like Befunge, INTERCAL or ITIL...


I think you're thinking of the very good book "Thinking FORTH", which presented an incremental style of development coupled with a Lispy DSL architecture.


Given that the second paragraph makes factually incorrect claims about what you can detect at compile time in dynamic languages, I think "debatable" is rather kind :)

Of course, sweeping generalizations about static versus dynamic languages tend to fall flat.


> I think "debatable" is rather kind :)

Well, I think he has an interesting data-point, and makes some interesting points despite maybe making a somewhat incorrect generalization.

But even that generalization points to something I find interesting, which is that Python and JavaScript (maybe Ruby as well) possibly form a separate group with the characteristics he describes.

I also found the observation that Mock object testing doesn't seem to respond well to changes in the underlying code interesting.

It's that mix that makes it "debatable" ;-)


There are some interesting, semi-recent (last few years) attempts to fix the pitfalls of using mocks for testing - renaming methods, wrong arity, typos, etc.

rspec-fire for example validates that the object you're mocking responds to the method name you've used, and takes the same number of arguments. It also does it conditionally based on whether the class in question has been loaded, so your unit tests can run in isolation, but when you run your full test suite it will catch any mocking errors.

Pretty handy!


Could you elaborate. Which claims are incorrect?


One example would be this:

> If you misspell a variable [...], you discover this at run time. Ouch.

CL-USER> (defun square (n) (* nn n)) ;; Ouch! nn isn't defined!

; in: DEFUN SQUARE ; (* NN N) ; ; caught WARNING: ; undefined variable: NN ; ; compilation unit finished ; Undefined variable: ; NN ; caught 1 WARNING condition SQUARE


Practically speaking, this is helpful, indeed.

Technically speaking, the more reflection a language provides, the less correct this becomes. Python code can dynamically inject local variables into stack frames, attributes into objects and so on in more or less silly and probably non-portable ways. So in the end, you end up with pragmatic correctness for mostly sane code. If that's enough for you and your project, go with it.


It is true of other dynamic languages and there isn't any way to fix that.


Sorry, many dynamic languages prevent against this. For example:

    $ perl -Mstrict -E 'say $foo'
    Global symbol "$foo" requires explicit package name at -e line 1.
Also, his comment about properties on objects is very debatable, too. Ignoring that the term "property" is overloaded and not defined, since I use roles (see: http://www.slideshare.net/Ovid/inheritance-versus-roles-1799...), I find out at compile time if my classes are missing an attribute or method (actually it's composition time, but that's splitting a hair that most will never notice). In short, I don't get frantic 2AM phone calls about a batch process trying to call a non-existent method.


  $ cat err.py
  def a():
    print b
  $ pylint -E err.py
  ************* Module err
  E:  2,8:a: Undefined variable 'b'


I politely submit that you have not heard of E.


There's also a continuum of how dynamic/static languages are, both regarding the syntax itself and idiomatic code. There's a bit of a difference between e.g. Ada and C, or Python and Scheme with macros turned to 11.

And the final recommendation for Go seems a bit odd, as I wouldn't really cite that as a language with a thorough, bullet-proof type system (not saying that it isn't worth recommending, but given the context of the post...)


>> And here is that insidious problem; all your unit tests have frozen the interaction of your components. They have copied the API of your components and distributed it all over your test code base.

This is what I hate about the unit-test everything approach. There seems to be a class of developers recently that believe robustness is more important than anything else.

Often speed bumps in changing API contracts is a higher price to pay [0] than the kind of bugs which unit testing uncovers.

There are lots of things which are good in small quantities and very bad in large quantities [1].

Better to be able to iterate quickly, spot a few bugs with sparse unit tests and completely rewrite your small components if they no longer fit the bill. There is a tendency (perhaps with those too familiar with large corporates) to try to de-risk with more and more robustness tests. With all of this robustness monolithic software flourishes, and you eventually lose the ability to iterate quickly; the ability to destroy and start anew.

[0] Form does not follow function, form is interdependent with function. Make the functionality robust and you make the form rigid! How will you find product-market fit when you are unable to move?!

[1] http://en.wikipedia.org/wiki/Hormesis


It's better to do the wrong thing quickly.


Eh, you'll have an annoying time putting a man on the moon with that attitude. But hey, your twitters will hockeystick better!

It all depends on your problem domain--the cost of failure for certain things (pacemakers, telco software, car engine ECU code, and so on) may be much, much greater than the cost of being super retentive in your development.


Yes, it depends on the domain but outside a few industries (and low-level code) software isn't a life-or-death matter.

Infrastructure is a different kettle of fish, while it might sometimes be a life-or-death matter, generally it's just safer to seek pure robustness here: monopolies with government connections do not need to fear losing competitiveness since they can (a) manipulate the rules of the market, and (b) bind market players with contracts.

I don't like your insinuation that I'm just talking about cool hockey-stick startups; in the domains in which non life-or-death, free-market business and software colide, losing competitiveness/product-market fit is the central failure mode. And you absolutely cannot become robust against the market. You must either (1) become faster than the market, or you must (2) control the market.


True indeed but the initial comment of 'It's better to do the wrong thing quickly.' was stated without nuance or caveat so the reply rightly pointed out that this statement is true in certain domains and completely false in others.


First of all, there are no silver bullets and no overall "better" languages. It depends on the situation, and a good software _engineer_ should be able to reason about the trade-offs of using statically/dynamically typed languages in each situation.

In this case, it is about shifting the costs to different stages of the projects lifecycle. Statically typed languages require more mental effort while developing, and, by definition, catch more bugs early in the process (compiler has more information to play with, and the programmer is required to be more thorough). Pushing it even further, formal proof systems require exponentially more effort at development time, and, in some cases, rewards it with the proof of software correctness (i.e. no bug-fixing costs at maintenance stage). On the other end of spectrum, dynamically typed languages allow for much faster development (which can be mission critical), but a rapid release comes with a price of long-term maintenance difficulties.

It's up to the developer to choose which approach is the most appropriate for the situation (amongst the myriad of other trade-offs, like performance, scalability, user requirements, existing infrastructure and so on).


The thing about dynamic languages is that they have a type system too, but it's called "inheritance." When literally everything in your language is an object that ultimately inherits from Object, as it is in Ruby or Python or Javascript, it doesn't matter what "type" each of your objects is, it just matters that your objects define the fields you want to access and respond to the methods you want to call on them (And Ruby even lets you fail up the object tree with method_missing). Fixating on types is focusing on the wrong thing. You need to understand the object inheritance tree, not the types.

Whether or not that makes your code unmaintainable is up to you, it helps if you keep your classes small and give each of them a well-defined and documented purpose. But that's just good coding practice in general.

Also, static code analysis is nice, but it can't determine everything about your code; Turing proved that in 1936.

Meanwhile, by skipping the the compiler and linker, you can deploy enhancements and bug fixes to a running program without shutting it down and restarting it. For a rather long but enjoyable read on why this is important for long-lasting software, see: http://steve-yegge.blogspot.com/2007/01/pinocchio-problem.ht...


Are so many bugs really caused by type problems? I run into type based errors in Python almost never. It is not hard to keep track of dynamic types, and takes the same effort (or less) than static typed programming. In both cases, you have to know what type you are working with, right?

If you have this problem you have probably been trained to be sloppy by compilers.


I often get burned by doing something like `key = "player" + playerId`. Unfortunately, playerId is an int, and I forget that even though most other languages besides C/C++ will convert that for me, Python will not, so I get a runtime exception. Same thing with `print "no space after this" + number`, because I get used to `print "space after this", number` and then sometimes I don't want the space and inevitably I forget to do `str(number)`.

Mind you, this is the sort of thing you usually discover quickly, but it's also the sort of thing that most other languages will catch at compile time.


Hey, you might not know this, but if you use a dynamic language with types you can actually use them to your benefit!

I've wrote a system in ruby that does clean type checking when I want it to and it cleans up the code and tests a lot.

Just because you're not taking full advantage of your language tools doesn't mean that "dynamic languages are unmaintainable"


How can you implement a static typing system in a language that is dynamically typed?


There is a difference between static typing and strong typing. Ruby is a dynamic language that is strongly typed. This will explain it better than I can:

http://www.rubyfleebie.com/ruby-is-dynamically-and-strongly-...


You can't. You can use type inspection and assertions to clearly delimit borders in your code, but it's really not the same thing.


I use a strongly, statically typed functional language. Because my code has no mutable state, the type system of the program proves the program correct. So I don't have to write any tests at all and maintenance is easy.


As someone who uses strongly, statically typed functional languages (dependent ones like Coq no less), and who rarely writes unit tests, I downvoted you because you're wrong. Specifically:

> the type system of the program proves the program correct.

Is not a valid statement, unless you're actually writing proofs in Coq. I bet you aren't.

"The type system of the program proves many simple properties of the program correct" is much more correct.

Also:

> Because my code has no mutable state

No, that's not why. The type system you use may be incapable of reasoning about mutable state, which coincidentally serves to indicate that it most likely can't reason about such things as "this list is sorted", further supporting my initial counterpoint.

Check out ATS or Why for examples of languages that permit reasoning about mutable state.


Which type system proves program correctness? A good type system can guarantee that your program doesn't suffer from certain classes of errors but that's very very far from correctness. Unless you only write programs that don't have any goal at all besides being memory safe: Behold MemSafe 2.9: it doesn't do anything at all, and most importantly, it doesn't do it in a memory safe way!


All safe type systems provide proofs that implementations fulfil specifications, but most type systems aren't expressive enough to create interesting specifications. The notable exceptions being type systems in dependently typed languages.


A strong type system can do more than prove memory safety. It can guarantee that their are no unhandled cases, and that data has been properly processed before going into a certain use; however it (generally) cannot guarantee correctness.

I say generally because the Haskell type system (with some extensions) is Turing complete, so you probably code implement a code prover in the type system.


Now and then I get to deal with development groups who have built their software systems using a language like Ruby or PHP. Many of them are proud of their extensive automated tests.

They'll brag about how they have 45,000 or more unit tests, for instance. Yet when we actually look at these tests, many of them just end up implementing a half-baked type system.

Even if they don't realize it, or don't want to acknowledge it, users of dynamic languages do want strong, static typing. It is one of the most useful tools there is when it comes to helping detect or avoid various problems. Rather than doing it in a sensible matter, by letting the computer (via a compiler, for example) efficiently do the hard or tedious work, they just choose to do it themselves manually.


As a Python programmer, I don't mind a type system that's actually useful and doesn't get in my way. Unfortunately, the most popular statically typed languages have shitty type systems. Maybe someday I'll learn Haskell and get a job writing in it, but until then, I'd rather keep my sanity.


Not sure if you're trolling or just don't understand what tests are actually supposed to test.


I'll bite. What are tests supposed to test?


That the code is doing what you wanted it to do, instead of what you actually wrote?


Well, they fail miserably then.


(define square ((a number)) :out number (+ a 2))

This program is arguably incorrect, even though the types match up and there is no mutable state.


Some languages can detect this at compile time. For example, ATS - a dependently typed language:

   fun square {m:int} (a: int m): int (m*m) = a + 2
This won't compile because in the type we've said the result must be the square of the input. I've written some more about using dependent types [1] and proofs [2,3] for this sort of thing.

The difference between writing proofs and writing tests is subtle though and serve similar purposes. Tests can make you feel confident that things are right but proofs should make you certain of it.

[1] http://bluishcoder.co.nz/2010/09/01/dependent-types-in-ats.h... [2] http://bluishcoder.co.nz/2013/07/01/constructing-proofs-with... [3] http://bluishcoder.co.nz/2012/10/04/implementing-a-stack-wit...


The program is correct, but the function name is wrong.


  >> don't have to write any tests at all
That is, until you find out that your state-machine matches incorrectly on an input text file...

  >> maintenance is easy.
...and you are the only person your company can find to maintain your codebase.

You're zero for two, my friend. :(


> ...and you are the only person your company can find to maintain your codebase.

Why should this be? Plenty of companies have teams of functional programmers.


Are you in the Valley or New York, by any chance?

No reason.


I'm thinking about writing about blog called 'Dogs Are Green.' I don't have any evidence, but it's my opinion.


Bad programming practices (which usually come from the business, in the form of unreasonable deadlines, untrained or unskilled programmers given high positions, and politically mandatory use of terrible legacy systems) are unmaintainable. I've seen static typing fall down just as hard when the developers weren't good.

Static typing is right for some problems, but the idea that dynamic languages are inherently unmaintainable in some way that most static languages aren't is one that I find incorrect.


Compiler does help with a lot of things that you'd require unit tests for with a dynamic language.

Example: Runtime wouldn't be able to give you a protocol between how two classes would communicate. Say your service object that handles authentication. You would have to write unit tests so the two classes involved would know how to respond to each other.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: