DEV Community

Cover image for Evaluating Developer eXperience of a programming language
stereobooster
stereobooster

Posted on • Edited on • Originally published at stereobooster.com

Evaluating Developer eXperience of a programming language

Ok, I need to explain myself. There is "The Joel Test". This is a "3-minute" test to evaluate the software team. The test itself is a bit outdated, for example, the question "Do you use source control?", it is like to ask do you brush your teeth - everybody should do that without questions, but I still like the test and consider it useful.

So I thought I will come up with the same kind of test to evaluate DX of programming language and tooling around it.

Does a language have a package manager?

There were times when the software was written from the scratch, in assembly language or using punch cards. But nobody writes software this way anymore.

Sussman said that in the 80s and 90s, engineers built complex systems by combining simple and well-understood parts. The goal of SICP was to provide the abstraction language for reasoning about such systems.

Today, this is no longer the case. Sussman pointed out that engineers now routinely write code for complicated hardware that they don’t fully understand (and often can’t understand because of trade secrecy.) The same is true at the software level, since programming environments consist of gigantic libraries with enormous functionality. According to Sussman, his students spend most of their time reading manuals for these libraries to figure out how to stitch them together to get a job done.
-- Programming by poking: why MIT stopped teaching SICP

Today's programming language should have a package manager, to install additional libraries and dependencies.

I guess one of the best examples of package managers out there is Bundler (Ruby). A lot of other package managers were inspired by it, like Cargo (Rust), Yarn (Node), and I guess Cocoapods (Objective-C).

Need to say that not every package manager is the same. There are some criteria for good package managers, for example, it should be able to cache packages (for faster installs), it should be deterministic (so each installation would be the same), it should be able to work offline (once all packages installed). For example, npm v3 is an example of not so good package manager, they fixed a lot of problems in the latest version. npm v3 is the reason why yarn exists, it was so hard to use that Facebook decided to create own package manager. And they keep improving it.

On the other side if authors of the language do not provide any option community will find a way around. I guess this is how Cocoapods were born, and it is kind worked out (I have no idea how popular widespread it is right now), but there can be and not so good outcomes when community splits, for example, The Saga of Go Dependency Management and Go 1.11 Modules (vgo) vs dep.

Reminder: my purpose here is not to judge or blame, this is just a case study of how things can go wrong, and to show how developers struggle when no attention paid to DX.

Does a language have a code formatter?

I guess syntax is the number one reason for bikeshedding. Not to bring hate, but just show how much energy can be wasted on this: bootstrap-dropdown.js clearMenus() needs ; at the end.
If there is no official style guide community can get fragmented, for example, “No semicolons” is the opposite of practical (JS again).

With good code formatter and integration in IDE, you don't need to worry about formatting at all - you can write anything and formatter will make it look nice.

Examples of formatters:

Worth to mention that linter is not a replacement for formatter, for example, eslint. No need to check that formatting is wrong and force developer to fix it when it is possible simply fix it. Leave all boring work to computers (and yes I know that eslint has --fix option, but it is slow and inconsistent compared to prettier).

There is a good scientific paper on this subject - A prettier printer
by Philip Wadler
. That is what they use in Prettier.

Does a language have a version switcher?

If you developing application most likely you have to deal with one version of the language. But if you have to deal with more than one project or develop library you may want to have more than one version of the language installed on the same machine and typically this is an issue because executables are named the same way. That is why you want to use some kind of "version switcher", application or shell function which can switch to different versions of programming languages.

Good version manager can:

  • switch version based on the preference in the directory, so each project can set the required version. For example, rbenv will switch version based on .ruby-version file.
  • can download and install the required version. For example, rustup install stable-x86_64-pc-windows-msvc

Examples of version switchers:

Worth to mention that with the rise of Docker this issue is less relevant recently.

Does a language have static analyzer?

Static analyzer - an application which will help to find bugs in your code before you ship it to production. It can be linter guarding against footguns or type checker.

Some languages are statically typed, so it is easy to do type checking, but it doesn't mean that you can't do static analysis for dynamically typed languages. There are a lot of examples of gradual type systems:

On the other hand, if the language is statically typed it doesn't mean that type checker will find all errors, for example, Infer can detect potential bugs in Java or C/C++/Objective-C.

Examples of static analyzers:

Does a language provide clear error messages?

I wrote about this earlier. I will not repeat myself, except that I consider Elm to be the best example in this category.

Does a language has a debugger?

Imperative languages

If language is imperative you can get away with print to stdout or console.log. This is not an ideal solution - I remember horror story about debugging JS in Internet Explorer with alert, later Firebug appeared, it was a big improvement of DX, and then Chrome DevTools Protocol.

Sometimes it is possible to use "common" (not language specific) tools, like GDB or strace, but it can be hard and it is better to have language-specific tools.

Examples of debug tools:

  • To debug Linux performance issues: linuxperf
  • To debug HTML/CSS/JS perfrormance issues: Chrome DevTools
  • To debug microservices: Jaeger
    • To debug goroutines gotrace
    • To debug Garbage Collector: ?

Declarative languages

But if the language is declarative you definitely want some tool for it, otherwise, you doomed. SQL is a good example of declarative language, and explain is debug tool, which will help you figure out why the query is slow. PostgreSQL tooling is specifically good.

On the other hand, there is no good debugger for CSS (which is also declarative language) and as a result, developers struggle with it.

There are some works in this field:

Does language have a standard library?

This is one is a bit vague. So I won't give the definition instead give some examples.

Don't:

Do:

  • I guess, Rust
  • Maybe Ruby to some extent

Does a language have learning resources?

At some point, you will need to land junior developer to your project and you will need to teach and help to adapt. And those learning resources first of all will save time for the senior developer, which would be able to send a link to some course and answer some questions instead of reading this course.

Again, a bit vague definition, so I'll provide examples:

Don't:

  • Flow type. I wasn't able to find anything useful besides official documentation

Do:

  • TypeScript. I found a lot of resource and introduction material on egghead.io and others

Does a language have a list of best practices?

A typical question from a newbie: what to use for X. Where X can be HTTP request, HTTP server, stream implementation, exotic data structure, ORM or anything else. What are the current best practices?

Don't:

  • JS is known for being oversaturated with choices. There is even term for it JavaScript fatigue. The situation got better recently with some convention over configuration trend set by create-react-app, and bestofjs appeared, but still, it is hard

Do:

  • A good example comes from the Ruby world, again. There is ruby-toolbox. It is not so glorious right now (after the incident), but believe me, it was.

Is a language environment easy to setup?

This is not always a trait of the language, sometimes rather a trait of projects in this language, but there is definitely a trend. It is fair to say that with Docker rise, this is less a problem, but still.

Some C and C++ projects have ridiculous requirements like you need make, cmake, GYP, python, node some dynamic libraries and all of that should be of some exact version otherwise it is incompatible. I understand there is a reason to have exact versions of dynamic libraries (or OS specific libraries), but otherwise, it is just bad DX.

PS

This is the first draft (to be precise the second, previous one is at my old blog). I guess I will need to update it after getting feedback. Did I miss something?

I hope I made myself clear - I don't want to shame or blame any of those languages or tools, I just wanted to show what is bad DX and what is good DX, so people can learn and improve. If you feel like I crossed the line, please tell me.

Photo by rawpixel on Unsplash

Top comments (8)

Collapse
 
rhymes profile image
rhymes

To be fair Go's standard library is quite good but as you said it can be better. Go's time library and HTTP server are great examples of the good parts of its standard library.

I'm not sure about the argument in the last section. brew install language doesn't seem that much of a barrier. The amount of dependencies of a project shouldn't be an issue attributed to the language itself though, did I understand correctly?

Collapse
 
stereobooster profile image
stereobooster • Edited

Agree on Go.

About last one - this is a bit hand-wavy metric. I can explain by example.

What you need to run a Ruby project. Ruby itself, Bundler (in Ruby 2.5+ it is included by default), and you are good to go. You may also need C compiler and some libraries, but not necessary. On average 1-2 dependencies, right?

What you need to build a C/C++ project. Based on my experience you will not get away with 1-2 dependencies it is more like 4-5 and all of them should of some exact version. For example, I tried to compile tree-sitter recently, here is Dockerfile

FROM ubuntu:trusty

RUN sudo apt-get -y update\
  && sudo apt-get -y install software-properties-common\
  && sudo add-apt-repository ppa:ubuntu-toolchain-r/test\
  && sudo apt-get -y update\
  && sudo apt-get -y install gcc-5 g++-5 clang\
  && sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-5 20\
  && sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g++-5 20\
  && sudo update-alternatives --config gcc\
  && sudo update-alternatives --config g++\
  && sudo apt-get -y install python make\
  && sudo apt-get -y install git-core

I need exact version of Ubuntu (not latest), exact version of gcc-5 and g++-5 (not latest). I need python, make, clang, ubuntu-toolchain-r/test. With Docker situation is better. Do you see what I mean?

Collapse
 
rhymes profile image
rhymes

Understood yes! I don't think you can escape that, after all "higher level" languages were created to ease the developer experience among other things :)

Collapse
 
dangolant profile image
Daniel Golant

Enjoyed this. Interesting to see how languages excel and lag in various places, particularly Go with gofmt vs package management. The whole "install packages from repo URLs" was really neat the first time I saw it, but the second you think about how it works it, you can see the flaws.

On the point about Docker making a version manager less important, I'll say that spinning up a container for every project when all you want to do is switch versions of a language is a bit overkill, in my opinion.

also, just a note, but you have a typo in your name in the title.

Collapse
 
stereobooster profile image
stereobooster • Edited

On the point about Docker making a version manager less important, I'll say that spinning up a container for every project when all you want to do is switch versions of a language is a bit overkill, in my opinion.

If we talking about languages which I work from day to day (Ruby and Node.js in my case) yes sure I would prefer version switcher, but If we talking about some experiments, like Rust or C++ which I want to compile down to WASM I would do it in Docker.

I fixed the title. Thanks.

Collapse
 
dangolant profile image
Daniel Golant

Totallly agreed.

Collapse
 
anurbol profile image
Nurbol Alpysbayev

I am so agree on Flow vs Typescript

Collapse
 
kopylov_vlad profile image
Vladislav Kopylov

Thank you. It is great article