7
\$\begingroup\$

I've made some code that generates cursed error messages like this, for my new language Javastack:

BadError on line 58:

S(Y5YN6c/$sIcI\"h>mH424`Ihkt&!3aolsjBHZ%\\J|
              ^

The problem isn't with the code. It's with you.

By cursed, I mean that they are random, out of context, useless, and have absolutely nothing to do with your code except for the fact that your code errored, so one of these is showing up.

And the code:

function genCursedError(){
    let rand = x => x[~~(Math.random() * x.length)], h, q, v;
    return `${rand(['Syntax','Reference','Type','Internal','You','Java','Error','Meta','Not An ','Interpreter','Random','Range','Muffin','Potential','Invisible','Bad'])}Error on line ${~~(Math.random() * 100)}:`+'\n\n'+
    `${[...Array(z = ~~(Math.random() * 50 +  30))].map(x=>String.fromCharCode(~~(Math.random() * 95 + 32))).join``}`+'\n'+
    `${' '.repeat(~~(Math.random() * z))}^`+'\n\n'+
    rand([
        `${rand(['This','Commas','Life','Screwdrivers','Water','Semicolons','You'])} cannot be used to ${rand(q=['eat','yeet','dissolve','use','remove','convert'])} ${rand(v=['potatoes','semicolons','you','peanuts','coffee','the digestive system'])}`,
         `Please don't ${rand(q)} ${rand(v)} - it causes ${rand(['cursed error messages like this one','bad puns','deconfrickulation of apioforms','Vitamin C','5th of July fireworks'])}.`,
         `Why did you do it? It's ruined I tell you, ruined!`,
         `Golfing tip: Use '${rand(h=['thrice','four','dynamite','add','concat','lyxal','rearrange','permutations'])}' instead of '${rand(h)}'.`,
          `The problem isn't with the code. It's with you.`,
    ])
}

It uses this basic template:

[Random error name]Error on line [Random number]

[Random ASCII]
[Pointer pointing to random location in ASCII]

[One of:
  [Random noun] cannot be used to [random verb] [random noun]
  Please don't [random verb] [random noun] - it causes [random thing].
  Why did you do it? It's ruined I tell you, ruined!
  Golfing tip: Use [random thing] instead of [random thing].
  The problem isn't with the code. It's with you.
]

Note that this code is in an imported module so has to be run in strict mode.

Is there anything I can do to make this code more readable and/or efficient?

\$\endgroup\$
3
  • \$\begingroup\$ Note that strings encased in backticks can go on for multiple lines, so you don't need the +'\n\n'+ stuff. However, it may be better to declare variables like const errorName = ... and then use those variables in the string instead of the expressions directly. (I'd write an answer, but I don't have much more advice and don't know JS) \$\endgroup\$
    – user
    Aug 7 '21 at 17:15
  • 1
    \$\begingroup\$ @user Thanks! According to CR policy, I'm not supposed to fix it here tho. \$\endgroup\$
    – emanresu A
    Aug 7 '21 at 20:44
  • \$\begingroup\$ I know, just suggesting some changes because I don't have enough for an answer. \$\endgroup\$
    – user
    Aug 7 '21 at 20:46
6
\$\begingroup\$

Efficiency

Since there is no real algorithm here to speak of, all optimizations are micro-optimizations and boil down to avoiding unnecessary object allocations.

Every call to genCursedError allocates and deallocates a function rand. This function is generic, so I'd move it to an enclosing scope, probably a separate module in a large project.

There are also arrays that are allocated on every call but don't change, ['This','Commas','Life'...] and so forth. Moving these to an enclosing scope is less urgent than rand because they don't do much good for any other function (as far as we know).

These could be default parameters, which are still allocated per call, but at least there's modularity there to justify the allocation. If your specification is fixed, as it seems to be, making them parameters seems premature.

Another option is a separate configuration object containing all of the string arrays organized by key.

I'd focus on maintainability and design principles rather than performance here.

Template readability

Based on the code, it's difficult to tell what the template is relative to the raw version, which is much easier to understand and modify.

Again, I'm not sure how generic this string should be -- as with the arrays of choices, it's basically baked into the function, which probably makes sense for your needs, but could be turned into a parameter. It'd be cool if the template string was basically verbatim as in the raw bottom quote and the values you wanted were injected without harming readability.

It's probably overkill, but if you're doing this sort of thing often, mustache is a lightweight templating library that might save work and improve readability, using established conventions like the {{ something }} syntax.

Short of that, I'd either parse the braces in the string (prioritizing readability of the template at the risk of introducing parsing logic) or use intermediate variables rather than inlining all of the random generation code.

Declarations and variable names

In most languages, like Ruby and C, rand doesn't choose a random element from an array, it generates a random float between 0 and 1, between 0 and an upper bound or between two bounds. I'd name this function choice (Python) or sample (Ruby).

Consider:

let rand = x => x[~~(Math.random() * x.length)], h, q, v;

One letter variables are generally poor practice, and it's pretty hard to make heads or tails of the intent here, other than rand and x. Always use const instead of let, unless you absolutely have to.

Avoid comma declaration lists. Each variable should be on its own line, and there's no need for forward declarations at the top of functions like ANSI C. Scope data as tightly as possible.

Avoid reassignments, especially in-line ones

Making matters worse, h, q and v are assigned later on inlined in the template in a function call to rand. I see no benefit of this approach over assigning those variables up front -- it's hard to tell when they're assigned and to what values. As a programmer, const gives me some assurance about the intent and state of the variable over time, so I don't need to trace control flow to determine what's what when. Immutability is even better, so if you Object.freeze your arrays, you have much stronger guarantees about what state the data is in at any given program point.

In fact, there's another variable, z, which is assigned inline but wasn't declared, meaning this is a global variable. If another function uses this variable, calling this function introduces a potentially very nasty bug due to the corrupted state. I'd check your strict mode declarations you claim to be using, because your original code should be rejected:

"use strict"; // <-- only thing added

function genCursedError(){
    let rand = x => x[~~(Math.random() * x.length)], h, q, v;
    return `${rand(['Syntax','Reference','Type','Internal','You','Java','Error','Meta','Not An ','Interpreter','Random','Range','Muffin','Potential','Invisible','Bad'])}Error on line ${~~(Math.random() * 100)}:`+'\n\n'+
    `${[...Array(z = ~~(Math.random() * 50 +  30))].map(x=>String.fromCharCode(~~(Math.random() * 95 + 32))).join``}`+'\n'+
    `${' '.repeat(~~(Math.random() * z))}^`+'\n\n'+
    rand([
        `${rand(['This','Commas','Life','Screwdrivers','Water','Semicolons','You'])} cannot be used to ${rand(q=['eat','yeet','dissolve','use','remove','convert'])} ${rand(v=['potatoes','semicolons','you','peanuts','coffee','the digestive system'])}`,
         `Please don't ${rand(q)} ${rand(v)} - it causes ${rand(['cursed error messages like this one','bad puns','deconfrickulation of apioforms','Vitamin C','5th of July fireworks'])}.`,
         `Why did you do it? It's ruined I tell you, ruined!`,
         `Golfing tip: Use '${rand(h=['thrice','four','dynamite','add','concat','lyxal','rearrange','permutations'])}' instead of '${rand(h)}'.`,
          `The problem isn't with the code. It's with you.`,
    ])
}

genCursedError()

Using const across the board eliminates the temptation to be overly-clever and make a mess with assignments inside expressions.

Avoid golfing idioms (unless you're actually golfing)

Don't abuse tagged templates for function calls to save 2 characters: join("") is the normal way; x=>String should be x => String; array elements should be separated by a single whitespace after commas; (){ should be () {; etc.

I'm also in the habit of using ~~() for flooring but it's better to use Math.floor. Anyway, this should only happen once in the code because you'll surely want to write a wrapper on Math.random() to give an integer between bounds.

You can avoid the rather ugly + '\n\n' + by merging the newlines into the nearest template literal.

Rewrite suggestion

This isn't a perfect rewrite; just a first pass attempt at refactoring for maintainability.

const randInt = n => Math.floor(Math.random() * n);

const sample = a => a[randInt(a.length)];

const randomString = n => [...Array(n)]
  .map(() => String.fromCharCode(randInt(95) + 32))
  .join("")
;

const cursedErrors = {
  errorNames: [
    'Syntax', 'Reference', 'Type', 'Internal', 'You', 'Java',
    'Error', 'Meta', 'Not An ', 'Interpreter', 'Random', 
    'Range', 'Muffin', 'Potential', 'Invisible', 'Bad'
  ],
  nouns: {
    upper: [
      'This', 'Commas', 'Life', 'Screwdrivers',
      'Water', 'Semicolons', 'You'
    ],
    lower: [
      'potatoes', 'semicolons', 'you', 'peanuts',
      'coffee', 'the digestive system'
    ],
  },
  verbs: [
    'eat', 'yeet', 'dissolve', 'use', 'remove', 'convert'
  ],
  phenomena: [
    'cursed error messages like this one',
    'bad puns', 'deconfrickulation of apioforms',
    'Vitamin C', '5th of July fireworks'
  ],
  golfingTips: [
    'thrice', 'four', 'dynamite', 'add', 'concat',
    'lyxal', 'rearrange', 'permutations'
  ],
  randomSentence() { 
    return sample([
      `${sample(this.nouns.upper)} cannot be used to ${sample(this.verbs)} ${sample(this.nouns.lower)}`,
      `Please don't ${sample(this.verbs)} ${sample(this.verbs)} - it causes ${sample(this.phenomena)}.`,
      `Why did you do it? It's ruined I tell you, ruined!`,
      `Golfing tip: Use '${sample(this.golfingTips)}' instead of '${sample(this.golfingTips)}'.`,
      `The problem isn't with the code. It's with you.`,
    ]);
  },
};

const generateCursedError = () => {
  const errorName = sample(cursedErrors.errorNames);
  const line = randInt(100);
  const ascii = randomString(randInt(50));
  const caretSpace = ' '.repeat(randInt(ascii.length));
  return `${errorName}Error on line ${line}:\n\n` +
    `${ascii}\n${caretSpace}^\n\n${cursedErrors.randomSentence()}`
  ;    
};

console.log(generateCursedError());

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.