Programming vs. expressing

Young enthusiastic intermediate coders instantly fell in love with problems: they would code many abstract objects, modules, bridges and extensive documentation, they would create a community around it, blogging about pseudoproblems.

I've really grown tired about those people. They believe that they are making the world better, but opposite is true. They flood the world with trivial codes made with OOP patterns. But patterns means something repetitive: if it is worth it, some day in some language a pattern is destined to evolve into expression. And expression simple enough that no programming is needed. If it is not worth it, then it is a mistake to program it at first place.

Let's demonstrate it on a task to load server-stored CSV data formatted by HTML template into some HTML container. This is complex enough (including threads) to show the point and simple enough to put it short.

Don't code yet, youngsters! Analyze first. And first of all realize what is not your business in this task:

You need the CSV file name, load it from server and line by line put each of them into the template and insert the merged output into container. Nothing more. Focus.

If you keep it as simple as possible, anybody can easily write his custom extension. If you make your script robust, community around it will see it as a blackbox and people in it will start to ask awkward questions like this one making the community reinventing wheels everywhere.

So let's analyze:

  1. We need to load CSV. So we code fetch(file). The result is a Promise of HTTP response.
  2. To extract the body from it, we continue .then(response => response.text()). The result is a Promise of string.
  3. We need to split the string into rows and handle each. We continue .then(data => data.split("\n").forEach(handleRow))
  4. The handleRow function will get a row, split it into columns and feed them another function that will do the rest. So we define it as let handleRow = row => container.innerHTML+= applyTemplate(...row.split(";"))
  5. The last thing we need is to take the arguments and put them into the template. We can use the native template system: string templates in backticks. To name the arguments, we can create a dynamic function let applyTemplate = eval(`(${cols})=>\`${template}\``) where cols is comma separated list of names given to the columns and used in the template.

Here the youngsters usually yell eval is evil!. They heard some influencers doing sharade with this catchy shortcut, they see the authorities nod when they hear it and they wish to get credit, too. When you ask them why, they autoreply security!. But when the CSV is on the server, it is the loader responsibility to secure it, so we delegate it. And when user input is not involved, the security is guaranteed. The real security issue is not knowing what kind of security is guaranteed and take absolute security for granted. Absolute security is a myth, so we should define the limitations instead:

Now let's put the above 5 steps into a simple code and create a function from it, providing the input variables as object properties:

csvLoad = config => { let applyTemplate = eval(`(${config.cols})=>\`${config.template}\``) let handleRow = row => config.target.innerHTML+= applyTemplate(...row.split(";")) fetch(config.file) .then(response => response.text()) .then(data => data.split("\n").forEach(handleRow)) }

If the limitations are not needed in every use-case and can be alleviated by extension, then code it separatedly or leave it to client programmers. The less code you write, the better. For instance, does anybody want to add an optional separator? Just replace ";" with config.sep||";". But don't code it, leave to client programmers if they want it or not. Maybe they want only comma separators, so they can replace it with "," so the complexity won't grow.

This program can be minified, but autominifiers suck in their effectivity: we can convert the program into a single-line named expression:

csvLoad=D=>fetch(D.file).then(r=>r.text()).then(d=>d.split("\n").forEach(C=> D.target.innerHTML+=eval(`(${D.cols})=>\`${D.template}\``)(...C.split(";")) ))

The simplest solution have 153 bytes, which is comparable to the 144 bytes of the problem description You need the CSV file name, load it from server and line by line put each of them into the template and insert the merged output into container.

The coding language is well-chosen and well-fit to the problem if the final code expression is roughly the same complexity as the original verbal description. But then we just translated (or encoded) the description in another language. We didn't write a program as an ordered set of instructions, we expressed it.

If the program can't be expressed, we should show some effort to simplify it. All thoughts are either simple or unclear: we should get rid of the unclear clutter (which is the root of confusion, which is expensive). Then the result is much less code, much less errors and much more effectivity, which is worth the time and thinking.

The conclusion is: don't write programs, use the right expression in appropriate language instead. The language is seldom programming.

Example

data source <div id="target"></div> <script> csvLoad({ file: "assets/data.csv", cols: "title, text", template: "<h3>${title}</h3><p>${text}", target: document.getElementById("target") }); </script>

Result