This post was written by Amin Shah Gilani, JavaScript developer for Toptal.

I love building things—what developer doesn’t? I love thinking up solutions to interesting problems, writing implementations, and creating beautiful code. However, what I don’t like is operations. Operations is everything not involved in building great software—everything from setting up servers to getting your code shipped to production.

This is interesting, because as a freelance Ruby on Rails developer, I frequently have to create new web applications and repeat the process of figuring out the DevOps side of things. Fortunately, after creating dozens of applications, I’ve finally settled on a perfect initial deployment pipeline. Unfortunately, not everyone’s got it figured out like I have—eventually, this knowledge led me to take the plunge and document my process.

In this article, I’ll walk you through my perfect pipeline to use at the beginning of your project. With my pipeline, every push is tested, the master branch is deployed to staging with a fresh database dump from production, and versioned tags are deployed to production with back-ups and migrations happening automatically.

Note, since it’s my pipeline, it’s also opinionated and suited to my needs; however, you can feel free to swap out anything you don’t like and replace it with whatever strikes your fancy. For my pipeline, we’ll use:

  • GitLab to host code.
    • Why: My clients prefer their code to remain secret, and GitLab’s free tier is wonderful. Also, integrated free CI is awesome. Thanks GitLab!
    • Alternatives: GitHub, BitBucket, AWS CodeCommit, and many more.
  • GitLab CI to build, test, and deploy our code.
    • Why: It integrates with GitLab and is free!
    • Alternatives: TravisCI, Codeship, CircleCI, DIY with Fabric8, and many more.
  • Heroku to host our app.
    • Why: It works out of the box and is the perfect platform to start off on. You can change this in the future, but not every new app needs to run on a purpose-built Kubernetes cluster. Even Coinbase started off on Heroku.
    • Alternatives: AWS, DigitalOcean, Vultr, DIY with Kubernetes, and many more.

Old-school: Create a Basic App and Deploy It to Heroku

First, let’s recreate a typical application for someone who isn’t using any fancy CI/CD pipelines and just wants to deploy their application.

Diagram of traditional code hosting and deploying actions

It doesn’t matter what kind of app you’re creating, but you will require Yarn or npm. For my example, I’m creating a Ruby on Rails application because it comes with migrations and a CLI, and I already have the configuration written for it. You’re welcome to use any framework or language you prefer, but you’ll need Yarn to do the versioning I do later on. I’m creating a simple CRUD app using only a few commands and no authentication.

 And let’s test if our app is running as expected. I went ahead and created a few posts, just to make sure.
The application running in development

And let’s deploy it to Heroku by pushing our code and running migrations

$ heroku create toptal-pipeline
Creating ⬢ toptal-pipeline... done
https://toptal-pipeline.herokuapp.com/ | https://git.heroku.com/toptal-pipeline.git
$ git push heroku master
Counting objects: 132, done.
...
To https://git.heroku.com/toptal-pipeline.git
 * [new branch]      master -> master
$ heroku run rails db:migrate
Running rails db:migrate on ⬢ toptal-pipeline... up, run.9653 (Free)
...

Finally let’s test it out in production

The application running in production

And that’s it! Typically, this is where most developers leave their operations. In the future, if you make changes, you would have to repeat the deploy and migration steps above. You may even run tests if you’re not running late for dinner. This is great as a starting point, but let’s think about this method a bit more.

Pros

  • Quick to set up.
  • Deployments are easy.

Cons

  • Not DRY: Requires repeating the same steps on every change.
  • Not versioned: “I’m rolling back yesterday’s deployment to last week’s” isn’t very specific three weeks from now.
  • Not bad-code-proof: You know you’re supposed to run tests, but no one’s looking, so you might push it despite the occasional broken test.
  • Not bad-actor-proof: What if a disgruntled developer decides to break your app by pushing code with a message about how you don’t order enough pizzas for your team?
  • Does not scale: Allowing every developer the ability to deploy would give them production level access to the app, violating the Principle of Least Privilege.
  • No staging environment: Errors specific to the production environment won’t show up until production.

The Perfect Initial Deployment Pipeline

I’m going to try something different today: Let’s have a hypothetical conversation. I’m going to give “you” a voice, and we’ll talk about how we can improve this current flow. Go ahead, say something.

Say what? Wait—I can talk?

Yes, that’s what I meant about giving you a voice. How are you?

I’m good. This feels weird

I understand, but just roll with it. Now, let’s talk about our pipeline. What’s the most annoying part about running deployments?

Oh, that’s easy. The amount of time I waste. Have you ever tried pushing to Heroku?

Yeah, watching your dependencies downloading and application being built as part of the git push is horrible!

I know, right? It’s insane. I wish I didn’t have to do that. There’s also the fact that I have to run migrations *after* deployment so I have to watch the show and check to make sure my deployment runs through

Okay, you could actually solve that latter problem by chaining the two commands with &&, like git push heroku master && heroku run rails db:migrate, or just creating a bash script and putting it in your code, but still, great answer, the time and repetition is a real pain.

Yeah, it really sucks

What if I told you you could fix that bit immediately with a CI/CD pipeline?

A what now? What is that?

CI/CD stands for continuous integration (CI) and continuous delivery/deployment (CD). It was fairly tough for me to understand exactly what it was when I was starting out because everyone used vague terms like “amalgamation of development and operations,” but put simply:

  • Continuous Integration: Making sure all your code is merged together in one place. Get your team to use Git and you’ll be using CI.
  • Continuous Delivery: Making sure your code is continuously ready to be shipped. Meaning producing read-to-distribute version of your product quickly.
  • Continuous Deployment: Seamlessly taking the product from continuous delivery and just deploying it to your servers.

Oh, I get it now. It’s about making my app magically deploy to the world!

My favorite article explaining CI/CD is by Atlassian here. This should clear up any questions you have. Anyways, back to the problem.

Yeah, back to that. How do I avoid manual deploys?

Setting Up a CI/CD Pipeline to Deploy on Push to master

What if I told you you could fix that bit immediately with a CI/CD? You can push to your GitLab remote (origin) and a computer will be spawned to straight-up simply push that code of yours to Heroku.

No way!

Yeah way! Let’s jump back into code again.

Diagram of a simple deploy CI/CD pipeline

Create a .gitlab-ci.yml with the following contents, swapping out toptal-pipeline for your Heroku app’s name:

image: ruby:2.4

before_script:
  - >
   : "${HEROKU_EMAIL:?Please set HEROKU_EMAIL in your CI/CD config vars}"
  - >
   : "${HEROKU_AUTH_TOKEN:?Please set HEROKU_AUTH_TOKEN in your CI/CD config vars}"
  - curl https://cli-assets.heroku.com/install-standalone.sh | sh
  - |
    cat >~/.netrc <<EOF
    machine api.heroku.com
      login $HEROKU_EMAIL
      password $HEROKU_AUTH_TOKEN
    machine git.heroku.com
      login $HEROKU_EMAIL
      password $HEROKU_AUTH_TOKEN
    EOF
  - chmod 600 ~/.netrc
  - git config --global user.email "ci@example.com"
  - git config --global user.name "CI/CD"

variables:
  APPNAME_PRODUCTION: toptal-pipeline

deploy_to_production:
  stage: deploy
  environment:
    name: production
    url: https://$APPNAME_PRODUCTION.herokuapp.com/
  script:
    - git remote add heroku https://git.heroku.com/$APPNAME_PRODUCTION.git
    - git push heroku master
    - heroku pg:backups:capture --app $APPNAME_PRODUCTION
    - heroku run rails db:migrate --app $APPNAME_PRODUCTION
  only:
    - master

Push this up, and watch it fail in your project’s Pipelines page. That’s because it’s missing the authentication keys for your Heroku account. Fixing that is fairly straightforward, though. First you’ll need your Heroku API key. Get it from the Manage Account page, and then add the following secret variables in your GitLab repo’s CI/CD settings:

  • HEROKU_EMAIL: The email address you use to sign into Heroku
  • HEROKU_AUTH_KEY: The key you got from Heroku
Image of the secret variables in the GitLab CI/CD settings page

This should result in a working GitLab to Heroku deploying on every push. As to what’s happening:

  • Upon pushing to master
    • The Heroku CLI is installed and authenticated in a container.
    • Your code is pushed to Heroku.
    • A backup of your database is captured in Heroku.
    • Migrations are run.

Already, you can see that not only are you saving time by automating everything to a git push, you’re also creating a backup of your database on every deploy! If anything ever goes wrong, you’ll have a copy of your database to revert back to.

Creating a Staging Environment

But wait, quick question, what happens to your production-specific problems? What if you run into a weird bug because your development environment is too different from production? I once ran into some odd SQLite 3 and PostgreSQL issues when I ran a migration. The specifics elude me, but it’s quite possible.

I strictly use PostgreSQL in development, I never mismatch database engines like that, and I diligently monitor my stack for potential incompatibilities.

Well, that’s tedious work and I applaud your discipline. Personally, I’m much too lazy to do that. However, can you guarantee that level of diligence for all potential future developers, collaborators, or contributors?

Errrr— Yeah, no. You got me there. Other people will mess it up. What’s your point, though?

My point is, you need a staging environment. It’s like production but isn’t. A staging environment is where you rehearse deploying to production and catch all your errors early. My staging environments usually mirror production, and I dump a copy of the production database on staging deploy to ensure no pesky corner cases mess up my migrations. With a staging environment, you can stop treating your users like guinea pigs.

This makes sense! So how do I do this?

Here’s where it gets interesting. I like to deploy master directly to staging.

Wait, isn’t that where we’re deploying production right now?

Yes it is, but now we’ll be deploying to staging instead.

But if master deploys to staging, how do we deploy to production?

By using something you should’ve been doing years ago: Versioning our code and pushing Git tags.

Git tags? Who uses Git tags?! This is beginning to sound like a lot of work.

It sure was, but thankfully, I’ve done all that work already and you can just just dump my code and it’ll work.

Overview of how staging and production deploys will work

First, add a block about the staging deploy to your .gitlab-ci.yml file, I’ve created a new Heroku app called toptal-pipeline-staging:



variables:
  APPNAME_PRODUCTION: toptal-pipeline
  APPNAME_STAGING: toptal-pipeline-staging


deploy_to_staging:
  stage: deploy
  environment:
    name: staging
    url: https://$APPNAME_STAGING.herokuapp.com/
  script:
    - git remote add heroku https://git.heroku.com/$APPNAME_STAGING.git
    - git push heroku master
    - heroku pg:backups:capture --app $APPNAME_PRODUCTION
    - heroku pg:backups:restore `heroku pg:backups:url --app $APPNAME_PRODUCTION` --app $APPNAME_STAGING --confirm $APPNAME_STAGING
    - heroku run rails db:migrate --app $APPNAME_STAGING
  only:
    - master
    - tags

...

Then change the last line of your production block to run on semantically versioned Git tags instead of the master branch:

deploy_to_production:
...
  only:
    - /^v(?'MAJOR'(?:0|(?:[1-9]\d*)))\.(?'MINOR'(?:0|(?:[1-9]\d*)))\.(?'PATCH'(?:0|(?:[1-9]\d*)))(?:-(?'prerelease'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?(?:\+(?'build'[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*))?$/
    # semver pattern above is adapted from https://github.com/semver/semver.org/issues/59#issuecomment-57884619

Running this right now will fail because GitLab is smart enough to only allow “protected” branches access to our secret variables. To add version tags, go to your GitLab project’s repository settings page and add v* to protected tags.

Image of the version tag being added to protected tags in the repository settings page

Let’s recap what’s happening now:

  • Upon pushing to master, or pushing a tagged commit
    • The Heroku CLI is installed and authenticated in a container.
    • Your code is pushed to Heroku.
    • A backup of your database production is captured in Heroku.
    • The backup is dumped in your staging environment.
    • Migrations are run on the staging database.
  • Upon pushing a semantically version tagged commit
    • The Heroku CLI is installed and authenticated in a container.
    • Your code is pushed to Heroku.
    • A backup of your database production is captured in Heroku.
    • Migrations are run on the production database.

Do you feel powerful now? I feel powerful. I remember, the first time I came this far, I called my wife and explained this entire pipeline in excruciating detail. And she’s not even technical. I was super impressed with myself, and you should be too! Great job coming this far!

Testing Every Push

But there’s more, since a computer’s doing stuff for you anyways, it could also run all the things you’re too lazy to do: Tests, linting errors, pretty much anything you want to do, and if any of these fail, they won’t move on to deployment.

I love having this in my pipeline, it makes my code reviews fun. If a merge request gets through all my code-checks, it deserves to be reviewed.

Image of Testing on every push

Add a test block:

test:
  stage: test
  variables:
    POSTGRES_USER: test
    POSTGRES_PASSSWORD: test-password
    POSTGRES_DB: test
    DATABASE_URL: postgres://${POSTGRES_USER}:${POSTGRES_PASSSWORD}@postgres/${POSTGRES_DB}
    RAILS_ENV: test
  services:
    - postgres:alpine
  before_script:
    - curl -sL https://deb.nodesource.com/setup_8.x | bash
    - apt-get update -qq && apt-get install -yqq nodejs libpq-dev
    - curl -o- -L https://yarnpkg.com/install.sh | bash
    - source ~/.bashrc
    - yarn
    - gem install bundler  --no-ri --no-rdoc
    - bundle install -j $(nproc) --path vendor
    - bundle exec rake db:setup RAILS_ENV=test
  script:
    - bundle exec rake spec
    - bundle exec rubocop

Let’s recap what’s happening now:

  • Upon every push, or merge request
    • Ruby and Node are set up in a container.
    • Dependencies are installed.
    • The app is tested.
  • Upon pushing to master, or pushing a tagged commit, and only if all tests pass
    • The Heroku CLI is installed and authenticated in a container.
    • Your code is pushed to Heroku.
    • A backup of your database production is captured in Heroku.
    • The backup is dumped in your staging environment.
    • Migrations are run on the staging database.
  • Upon pushing a semantically version tagged commit, and only if all tests pass
    • The Heroku CLI is installed and authenticated in a container.
    • Your code is pushed to Heroku.
    • A backup of your database production is captured in Heroku.
    • Migrations are run on the production database.

Take a step back and marvel at the level of automation you’ve accomplished. From now on, all you have to do is write code and push. Test out your app manually in staging if you feel like it, and when you feel confident enough to push it out to the world, tag it with semantic versioning!

Automatic Semantic Versioning

Yeah, it’s perfect, but there’s something missing. I don’t like looking up the last version of the app and explicitly tagging it. That takes multiple commands and distracts me for a few seconds.

Okay, dude, stop! That’s enough. You’re just over-engineering it now. It works, it’s brilliant, don’t ruin a good thing by going over the top.

Okay, I have a good reason for doing what I’m about to do.

Pray, enlighten me.

I used to be like you. I was happy with this setup, but then I messed up. git tag lists tags in alphabetical order, v0.0.11 is above v0.0.2. I once accidentally tagged a release and continued doing it for about half a dozen releases until I saw my mistake. That’s when I decided to automate this too.

Here we go again

Okay, so, thankfully, we have the power of npm at our disposal, so I found a suitable package: Run yarn add --dev standard-version and add the following to your package.json file:

  "scripts": {
    "release": "standard-version",
    "major": "yarn release --release-as major",
    "minor": "yarn release --release-as minor",
    "patch": "yarn release --release-as patch"
  },

Now you need to do one last thing, configure Git to push tags by default. At the moment, you need to run git push --tags to push a tag up, but automatically doing that on regular git push is as simple as running git config --global push.followTags true.

To use your new pipeline, whenever you want to create a release run:

  • yarn patch for patch releases
  • yarn minor for minor releases
  • yarn major for major releases

If you’re unsure about what the words “major,” “minor,” and “patch” mean, read more about this at the semantic versioning site.

Now that you’ve finally completed your pipeline, let’s recap how to use it!

  • Write code.
  • Commit and push it to test and deploy it to staging.
  • Use yarn patch to tag a patch release.
  • git push to push it out to production.

Summary and Further Steps

I’ve only just scratched the surface of what’s possible with CI/CD pipelines. This is a fairly simplistic example. You can do so much more by swapping out Heroku with Kubernetes. If you decide to use GitLab CI read the yaml docs because there’s so much more you can do by caching files between deploys, or saving artifacts!

Another huge change you could make to this pipeline is introduce external triggers to run the semantic versioning and releasing. Currently, ChatOps is part of their paid plan, and I hope they release it to free plans. But imagine being able to trigger the next image through a single Slack command!

Diagram of a CI/CD deployment pipeline where production deploys are triggered externally, possibly via chat or webhooks

Eventually, as your application starts to grow complex and requires system level dependencies, you may need to use a container. When that happens, check out our guide: Getting Started with Docker: Simplifying Devops .

This example app really is live, and you can find the source code for it here.

This post was written by Phillip Brennan, Software Developer for Toptal.

Regardless of the apparent evidence to the contrary, programmers are humans. And, as all humans, we like taking advantage over our freedom of choice. Whether that choice is about taking the red pill or the blue pill, wearing a dress or pants, or using one development environment over another, the choice we make places us in one group of people or another. Choice, inevitably, comes after our evaluation of options. And having made a choice, we tend to believe that anyone who chooses differently made a mistake.

You can easily search the internet and find hundreds of debates about Emacs vs Vim. Even if you read them all, it will be impossible to objectively choose a winner. However, does the choice of development environment tell you anything about the quality of work a developer can deliver? Absolutely not!

great developer could write her code into Notepad and still deliver great stuff.

Certainly, there are a lot of things professionals consider when selecting tools for their work. This is true for every profession, including software development. Quite often, however, selection is based on personal taste, not something easily tangible.

Programmers spend most of their time looking at the development environment, so it is natural that we want something pretty as well as functional. Every development environment has its pros and cons. As a whole, they a driving force of the software development industry.

best programming editors

What are the things a developer should evaluate when choosing a set of programming tools like a programming editor of choice? The answer to this question is not as simple as it might sound. Software development is close to an art, and there are quite few “fuzzy” factors that separate a masterpiece from an overpriced collectable.

Every programming language, be it Java, C#, PHP, Python, Ruby, JavaScript, and so on, has its own development practices related to project structure, debugging, and deploying. However, one thing they all have in common is editing code. In this article we will evaluate different development platforms from the perspective of the most common task in software development: writing code.

IDE vs General Purpose Text Editor

An integrated development environment (IDE) (or interactive development environment) is a software application that provides comprehensive facilities to computer programmers for software development. An IDE normally consists of a source code editor, build automation tools, and a debugger, and many support lots of additional plugins and extensions.

Text editors are simpler applications. Compared to IDEs, they usually correspond to just the code editor segment of an IDE. However, they are often much more than that. IDEs are created to serve the purpose of software development, while many text editors are designed to be used by non-developers as well.

Static-typed languages can get a lot of benefits from IDEs. Because of the strict typing rules, it is possible for the IDE to detect bugs and naming inconsistencies across classes and modules, and even across files, directly in the editor, before compiling. This functionality comes standard with many IDEs, and for that reason, IDEs are very popular for static-typed languages.

However, it is impossible to do the same thing for dynamically typed languages. For example, if a method name may be generated by the code itself, constructed from a series of string concats, trying to detect naming errors in dynamic languages requires nothing less than running the actual program. Because one of the major benefits of IDEs does not apply to dynamic language programmers, they have a greater tendency to stick with text editors like Sublime. As a side note, this is also a major reason why the test-driven development movement has grown up around dynamic language communities, and has not had as strong of a following in static languages.

What Makes a Great Programming Editor?

Aside from a number of different features for various languages, every programming editor needs to have a well-organized and clean user interface. Overall aesthetic appeal should not be overlooked, either. It is not just a matter of looking good, as a well-designed editor with the right choice of font and colors helps keep eyestrain down and lets you be more productive.

In today’s development environment, a steep learning curve is a liability, regardless of feature set. Time is always valuable, so a good editor should be easy to get used to. Ideally, the programmer should be able to start work immediately, without having to jump through too many hoops. A Swiss army knife is a practical and useful tool, yet anyone can master it in minutes. Likewise, for programming editors, simplicity is a virtue.

User Interface, Features, and Workflow

Let’s take a closer look at UI, different features and capabilities, and frequently used tools that should be a part of any programming editor.

Line numbers, of course, should be on by default and simple to turn on or off.

Snippets are useful for inserting standardized blocks of text in a fixed layout. However, programming is a lot about saying things just once, so be careful with snippets as they might make your code hard to maintain in the future.

The ability to lint, or syntax-check, the current file is useful, as is the ability to launch it. Without this facility, a programmer must switch to an external command line window, choose and run the correct command, and then step through error messages to find the source of the error. However, the linting must be under the programmer’s control, because the delay incurred by the lint might interrupt the coder at a crucial moment.

inline doc

Inline doc is useful as long as it does not get in the way, but having a browser page open on the class definitions is sometimes more useful, especially when there are lots of related classes that do not directly extend each other. It is easy enough to cut and paste code from the browser documentation to the code being written, so the additional complexity of inline documentation often becomes less useful, indeed, more annoying, as the programmer’s knowledge of the documentation increases.

Word-completion is helpful since it is fast, and almost as reliable as in-edit documentation, while being less intrusive. It is satisfying to enter just a few characters of a word and then hit enter to get the rest. Otherwise, one labors under the strain of excess typing, abhorred by lazy programmers, who want to type ee rather than the more lengthy exponentialFunctionSquared. Word completion satisfies by minimizing typing, enforcing coherent naming and by not getting in the way.

Renaming variables and functions across the program is useful, but you need to be able to review changes and make sure your code is not broken. Again, word completion is a useful halfway house, in that it works for all languages; you can use long names for items that have long lifetimes, without incurring a typing overhead. You can use references to them via a shorter name locally, in order to shorten expressions which might otherwise spread over too many lines. If you need to rename, the long names are unique, so this approach works across all languages, and all files.

Source files can sometimes grow a lot. Code-folding is a nice feature that simplifies reading through long files.

Find/change with scope limitation to local, incremental, or global with meta characters and regular expressions are part of the minimum requirement these days, as is syntax highlighting.

Over the years, I went through a number of editors, and this is what I think of them:

  • Emacs: One of the most popular editors in the world. Emacs’ greatest feature is its extensibility, despite the complexity of its extension language (you can even play Tetris in it with M-x tetris). Emacs fans consider its terminal-based interface to be a great feature, while others might debate that it’s a drawback. In my personal experience, I found it too much to adopt and learn. I am sure that if you know how to use Emacs you will never use anything else, but to take on and learn the entire culture was more than I wanted to do. Nevertheless, its popularity among developers proves that it is far from being a relic of the old times, and remains part of our future as well.
  • Vi/Vim: Vim is another powerful terminal-based editor, and it comes standard with most xNIX operating systems. Apart from having a different interface than Emacs, my view is practically the same. If you grew up on it, I am sure you will never use anything else. Having Vi skills will make your life much simpler when operating through SSH and other tight spots, and you wont have problems with speed, once you get familiar with keystrokes. While not as tough to crack into as Emacs, the learning curve is still quite steep, and it could definitely use few nice features of a windowed editor.
  • SublimeText: True to its name, SublimeText is a beautiful text editor with tons of features. Unlike some similar editors, SublimeText is closed source, so it cannot be modified at a low level. SublimeText offers the simplicity of traditional text editors, with a lean and fast UI. Many developers find it easier to use than Vim, and this is especially true of newcomers. The learning curve just isn’t as steep. While the UI is minimal and straightforward, SublimeText does offer a few nifty features, such as a scaled down display code on the right of the UI, allowing users to quickly scroll through their code and navigate with relative ease. While it’s not completely free, the feature-limited demo version is. Unlocking all the features will cost you $70.
  • Atom is the result of a GitHub effort to produce a programming editor for a new generation of developers. While it is still a work in progress, Atom is a very capable editor with a vibrant community of developers keen on new extensions, JavaScript libraries and more. It’s downsides include some UI quirks, the possibility that some add-on packages could misbehave, and reported performance issues when working with (very) big files. But the project is under active development, and current shortcomings are likely to be improved. Atom is an open source project, and it can easily be hacked to suit your needs.
  • Nano: Excellent in a tight corner, but not feature-rich enough to prevent the inevitable thought creeping into one’s mind that there must be faster way to do this as one struggles through the keystrokes to indent a block of code, while keeping the comments lined up in column 80! It does not even have text highlighting, and should not be used for anything more than config file changes.
  • TextMate2: TextMate’s biggest drawback is that it only runs on Mac. As its creators put it, “TextMate brings Apple’s approach to operating systems into the world of text editors.” By bridging UNIX underpinnings and GUI, TextMate cherry-picks the best of both worlds, to the benefit of expert scripters and novice users alike. It is the editor of choice for many Ruby, Python, and JavaScript developers, with great support for Bash or Markdown as well. At the moment of publishing this article TextMate 2 is still in Beta, but it already has a very mature plugin ecosystem that promises to extend it even beyond Emacs’s extensions.
  • jEdit: Java-based, and considered slow by some. Out of the box configuration might push certain people away, but jEdit can be extremely fast if configured properly, as well as extremely nice looking.
  • Eclipse: Another widely used IDE, Eclipse is very popular among Java developers, but has been adapted to many different platforms. We could argue that its monolithic architecture is a rock that will pull it under the water, but it is still one of the most popular platforms among developers.
  • Aptana Studio: A comprehensive open-source web application IDE. It is available as an Eclipse plugin, which makes it popular among some Java developers. The standalone version is even leaner, and offers a range of different themes and customization options. Aptana’s project management capabilities may also come in handy to coders who honed their skills in Eclipse. While earlier versions suffered from performance issues on some hardware platforms, these problems were addressed in Aptana Studio 3, and should be a thing of the past.
  • NetBeans: Another relatively popular open-source IDE with cross-platform support. It is somewhat slower on startup than lean editors like SublimeText, and the choice of add-ons is limited compared to some alternatives. Many Java developers have grown to love NetBeans thanks to seamless SCM integration and HTML5 support. NetBeans support for PHP has also improved in the latest releases.
  • JetBrains: Offers a family of IDEs for Java, Ruby, Python and PHP. They are all based on the same core engine. Very capable in its own right, JetBrains IDEs have been gaining a growing following. However, they are not free, open-source solutions, although a 30-day trial is available, and pricing is reasonable.
  • Komodo Edit: Komodo Edit has great potential, and yet it’s full of annoying little “gotchas” and idiosyncrasies that can be frustrating by its lack of orthogonality. Komodo Edit feels cluttered, which is a shame because it clearly has immense capability. I keep going back to Komodo Edit in the hopes that I have missed some organizing principle, and every time, I am beaten back by a welter of disorganized capability.
  • Geany: Geany is not a major power player like many of the other editors in this list. It is defined more by “what it is not” than “what it is.” It is not slow, it does not have a lot of heritage from the old days, it does not have a macro capability, or much of a multi window on buffer capability. Yet the things it does do, it does well enough. It is, perhaps, the least demanding of all the editors that I tried and still can do 90 percent of what you would expect from a programmer’s editor. Geany looks good enough on Ubuntu, which is one of the reason I chose it as my preferred editor.

My Conclusion

It would be presumptuous to declare just one as the best programming editor among these great tools. And there are quite a few editors I did not even try. There is no one-size-fits-all solution. This is what compelled me to try out a number of different editors.

I currently am using Geany, but it’s because it fits the requirements I have. With Geany, and a lot of help from Perl/Gimp/Audacity/Sox, I am able to develop and maintain the Java code-base for the Android apps I develop, prepare them for compilation in different configurations for multiple distributors, source, lint, compile, dex and produce .apk files, and deliver these apps globally.

Your line of development might set a different set of requirements, and I hope I saved you some time in researching for the most appropriate programming editors.