Sponsored Link •
The initial productivity gain of working with a dynamic language can decline as a project's codebase grows, and as refactoring becomes increasingly a chore.
Many arguments in favor of dynamic languages center around improved developer productivity. Productivity, however, is a loosely-defined term. For example, productivity can vary over the lifetime of a project: being productive today no more guarantees tomorrow's productivity than today's seeming inefficiency assures failure and waste in the future.
I've heard enough about productivity brought about by dynamic languages to want to test this premise first-hand. So when a client approached me about a tiny application that would be used internally by about twenty people, that sounded like the perfect opportunity for a dynamic language project: It would allow me to learn Rails, something that was high on my wish list, and to enjoy the productivity benefits of Ruby at the same time.
Coming from a Java background, I was very pleasantly surprised not just, or even primarily, with Rails, but especially with Ruby: There seemed to be a Ruby library for all the tasks I needed to accomplish, from connecting to an ODBC data source, to authenticating with Microsoft Exchange and fetching mail from an IMAP server, to generating simple PDF reports. And I could do all that in just a few lines of clean, neatly organized code. Ruby's conciseness and elegance, combined with Rails, allowed me to develop and put the application in production in only a few weeks, working part-time on the project.
Once deployed, the application worked very well. So well, in fact, that a few months into production, the client became interested in expanding the application's features. They agreed to a method whereby we would plan for and deliver each desired feature without a big, up-front design, since they were uncertain about the specifics of the features.
I'm a big believer in such an agile approach, but my experience in developing software that way was at the time limited to Java. With that background, I was confident that frequent refactoring, coupled with good unit and functional tests, would make this approach practicable and fruitful. My confidence in the agile way of doing things was bolstered by several Rails features, such as migrations—small Ruby tasks that affect changes to the database schema—as well as Rails' extensive and well thought-out testing framework.
These tools, along with the relatively small amount of Ruby code required to implement the desired features, made life easy during the first few iterations. With each new feature, however, the amount of refactoring required to keep the codebase clean increased. Although Rails' adherence to the DRY principle helped reduce duplication, each refactoring had to change numerous code artifacts. Some of the changes, admittedly, were needed to keep the code in line with Rails' convention-over-configuration philosophy.
Renaming an entity class, for instance, requires renaming the corresponding database tables, including other columns and indexes referencing the model's table, renaming the model class, changing the corresponding controller, helper classes, and every reference to the entity class in the views, as well as renaming the directory containing the view classes and templates. And, of course, you have to similarly rename the associated tests and all references to the model, controller, and views in the tests and fixtures.
Most of these refactorings, such as changing database table names, are not mandated by Ruby or even Rails. And Rails provides easy overrides for every one of its conventions. Still, I didn't want to start down the slippery slope of masking changes in design with configuration overrides, and decided to just take the time to perform every refactoring the right way.
A few days ago, after having spent many hours renaming things, using search-and-replace, and fixing tests, I was itching for a better way. Even the excellent RAD Rails IDE was of little help, since it is not at present able to rename even Ruby classes without breaking references to those classes. Some of the features that made development fast in the early stages of the project, were now slowing me down.
Some would argue at this point that the primary cause for this productivity decline at this stage of the project is that IDEs just can't refactor dynamic code as well as they can statically typed code. That, however, is only part of the reason, I think.
I wanted to ensure that my refactorings were correctly and consistently performed across the entire application stack mainly because I felt that in the absence of a compile-time type-checking system coding conventions became important crutches to lean on for comfort. For the same reason, I'm also more reliant on tests in this dynamic-language project, and tend to write more tests than I would create for, say, a Java application. And, of course, refactoring tests is a large part of the effort at this point in the project.
If I were to generalize my admittedly limited experience working on a growing codebase in a dynamic language, I would argue that dynamic languages do not necessarily lead to harder-to-refactor code. Rather, it's the infrastructure needed to develop larger applications in dynamic code that make refactoring more of a chore, especially as the codebase grows. Coding conventions and tests are, in a way, the "type system" of a dynamic project in terms of providing guarantees—a sort of contract—a developer can depend on.
I wonder to what extent that infrastructure will get in the way of productivity as this projects grows. While that ongoing overhead may be significant, one could look at it in a way one would consider an investment portfolio: Over a longer period, such a year, it is not the marginal gain or loss in the price of any one equity that counts, nor the loss or gain within a shorter time segment, but the total return on the portfolio over the longer period. A huge gain in the first few months may be neutralized by subsequent declines, and vice versa. In a similar way, it may be that even if my productivity declines over time, it will still exceed what I would have had I chosen a strongly-typed language for this project.
What is your experience with sustained productivity when working with dynamic languages on larger projects? How do you refactor larger code-bases written in a dynamic language?
|Frank Sommers is a Senior Editor with Artima Developer. Prior to joining Artima, Frank wrote the Jiniology and Web services columns for JavaWorld. Frank also serves as chief editor of the Web zine ClusterComputing.org, the IEEE Technical Committee on Scalable Computing's newsletter. Prior to that, he edited the Newsletter of the IEEE Task Force on Cluster Computing. Frank is also founder and president of Autospaces, a company dedicated to bringing service-oriented computing to the automotive software market.
Prior to Autospaces, Frank was vice president of technology and chief software architect at a Los Angeles system integration firm. In that capacity, he designed and developed that company's two main products: A financial underwriting system, and an insurance claims management expert system. Before assuming that position, he was a research fellow at the Center for Multiethnic and Transnational Studies at the University of Southern California, where he participated in a geographic information systems (GIS) project mapping the ethnic populations of the world and the diverse demography of southern California. Frank's interests include parallel and distributed computing, data management, programming languages, cluster and grid computing, and the theoretic foundations of computation. He is a member of the ACM and IEEE, and the American Musicological Society.