From heavyweights like Visual Studio and Eclipse to more lightweight alternatives like Atom and CodeLite, Integrated Development Environments, or IDEs, continue to persist into the modern era as an "essential" tool of software development.
I must disagree.
Over the last two decades, I've written code in a variety of contexts, using different languages, libraries, and frameworks. I've written PHP, C, Java, Ruby, Perl, Common Lisp, Scheme, Python, Javascript, and more, building medium- to large-scale systems that run in production. I've developed for a bevy of old-school UNIX platforms, Linux, BSD, and the web.
And I have never found IDEs to be helpful to me, as a programmer.
The "Benefits" of an IDE
When pressed, IDE advocates usually offer up the following bullet-points as benefits of working inside of an IDE:
- Code Completion
- Automatic Code Generation
- Tools Integration (compiler, debugger, VCS, etc.)
(I have left out the vague and unquantifiable reasons like "they help boost productivity" and "they makes things better", and the nonsensical responses like "how else do you write the codes?")
Each of these so-called benefits is in fact only beneficial to beginning and/or mediocre developers.
Popup-Coding (Code Completion)
Given that the editor actually understands the code being written, and has a deep knowledge of the language and its faculties, code completion sounds like a great idea. If I start with the following snippet of C, for example:
#include <stdio.h>
int determine_mode(int n, char **args);
int main(int argc, char **argv)
{
int apples = 42;
/* next line intentionally incomplete... */
int mode = determine_mode(
return 0
}
... and then type the character a
after the opening parenthesis of the call to determine_mode
, the IDE can helpfully popup a list of possible variable names. In this case, apples
or argc
(argv
is not a candidate because the first argument to the function must be an integer, not a character pointer)
Apply this to function invocation (auto-complete the function name) and object / struct member names (what are all those members called, anyway?!?) and you've got yourself a pretty slick and helpful tool for getting code written faster. After all, it takes a non-zero amount of time to press keys, so the fewer keystrokes it takes to get code, the better off you'll be, right?
Right?
Wrong.
Sure, popup-coding helps people unfamiliar with the code base, or the library, or even the language, but they will never move beyond the need for the code-completion crutch. I know (several) professional programmers out there who cannot write code without the editor telling them where to go next.
It's no secret that there's limited space in between your ears, and everything else is always vying for that precious real estate. When do I have to pick up the dry cleaning? What's left to do to meet that deadline? Why is this stupid race condition so hard to track down?
When you externalize bits of the library (what arguments does this function take? What is its return value?), or worse, bits of the language itself (how do for
loops work? What's the syntax for a goroutine?), you push them out of your head and into the editor. Which is precisely the wrong thing to do when you want to excel at software development.
Instead, professional programmers should train their minds to be able to hold more information about the problem at hand in their head. Contrary to popular belief (and an unspoken foundation of IDE enthusiasm) you can strengthen your mind to remember more.
The Boilerplate (Code Generation)
Some IDEs offer a productivity panacea that's even more potent (and alluring) than popup-coding: Code Generation.
It works like this: you fill out a form, click a few buttons, and -poof- scads of code appears with almost no effort. Pretty cool, eh?
In its more usable form as Automatic Programming, yes, code generation is pretty nifty. But most IDEs get it wrong in a few very important ways.
For starters, code generation is usually a one-time event. This is just glorified templating, scaffolding or, as I like to call it, boilerplate. Boilerplate is bad because requirements change over time, and the code so-generated must also change. Sadly, subsequent change to generated code is almost always manual, which obliterates any value gained from the initial productivity boost.
There are ways around this. You can write code that in turn writes more code. If you do that outside of the programming language it's called a domain-specific language or DSL. If you embed it into the language, it's a macro (Lisp-style, not C-style).
This brings me to my second gripe with IDE code generation tools: a good automatic programming facility should live outside of the editor, and become part of either the language itself (as a macro) or the codebase (as a DSL). The editor is the wrong place for it.
Tools Integration... or Tools Hiding?
Code - compile - debug - commit, all from the comfort of your editor!
Actually, that does sound like a good idea. Too bad IDEs go to far. This time, instead of insulating you from how your code and/or language actually work, they shield you from the messy details of how your software gets built, how to invoke the debugger, how to navigate your version control system, etc.
To their credit, IDE writers really only have two options in this space: write their own compiler / debugger / version control system and embed it directly in the IDE (Microsoft / Borland), or attempt to integrate external tools as-is (Eclipse, mostly).
This is a lose-lose situation.
If you go with the "custom tools" solution, you are going to get an inferior compiler / debugger / version control system. The people writing the IDE have limited time and effort to focus on the task of writing the IDE itself, which usually means the tooling suffers. Turns out that compiler-writing is hard, and requires a special brand of hacker to pull off correctly. Debuggers are intimately intertwined with the compiler, since they have to understand the runtime, stack discipline, etc.
If you opt for the external integration, you also lose out. Sure, you're using GCC / GDB / git / what-have-you, each with their own teams of smart and dedicated people making them great, but the IDE necessarily limits how you get to use those tools. This may work out great for the beginner programmer, who just wants to focus on the code, but it hides the tools and thereby hinders advancement.
The point is, any tool you can embed into your IDE is a tool you'd be best served learning how to use on its own. Some really powerful stuff can be done with tools like gdb
and git
.
Other Problems
IDEs have some pretty insidious problems baked right-in.
I once saw a codebase that had defined a function for retrieving objects from a database. For sake of illustration, I'm going to call those objects Things
. Here's the function header (in Go, body omitted for brevity):
func (db *Database) GetExstingThings() ([]*Thing, error) {
/* ... */
}
While doing the code review, I made a mental note that this function was probably cruft, and not used, because Existing
was misspelled. Imagine my surprise when I found several (dozens or more) calls to this function, all with the same typo!
That's popup-coding if ever I've seen it. G
-e
-t
-E
-x
-screw-it-that's-close-enough-⏎
.
Sure, it's an innocent enough mistake, and one that search-and-replace (or better yet, those refactoring tools I keep hearing so much about) would make quick work of. But the problem goes deeper than that.
Not having to type the names of functions, or remember the order of parameters leads to bad design. If I rely exclusively on auto-completion logic, I have no incentive to come up with compact and concise function names; I'll just let the editor type the rest of ThatReallyLongFunction's name. This in turn leads to half-hearted attempts at finding good abstractions, since the point of analogy and abstraction is to reduce the cognitive load on the programmer through well-known patterns.
One complaint I often hear about libc, the C standard library, is that functions like memcpy
are confusing with respect to parameter order. Here are the two possibilities:
void memcpy1(void *src, void *dst, size_t n)
void memcpy2(void *dst, void *src, size_t n)
(spoiler: it's the second one)
IDE supporters are quick to point out that code-completion makes this a moot point; the editor knows the type signature and the documentation for the function, so you don't have to.
There's a trick to memcpy
that makes it easy to remember. The destination comes before the source argument, which mimics assignment in C:
char *a, *b;
a = strdup("Hello, World")
/* b = a */
memcpy(b, a, strlen(a))
You know what's really cool? Once you know and understand the "mimic assignment" convention, you can apply that to other functions with source / destination semantics, like memmove
, strcpy
, etc. On top of that, you can now spot lurking bugs in the code that auto-completion won't show you, like this:
char *a, *b
a = strdup("Hello, World")
memcpy(a, b, strlen(a))
If you didn't know that the memcpy
line translates to a = b
, you'd miss the stack overflow bug waiting in the dereference of the uninitialized b
pointer.
Closing Remarks
IDEs promise the world and deliver something (I guess). That something isn't something you want as a professional. Masking complexity, hiding tools, encouraging bad design; all these things are bad.
If you want to become a better programmer, ditch the IDE and learn to use your language's toolchain.