Hacker News new | ask | show | jobs
by hypertele-Xii 1748 days ago
> The compiler should force you to handle every exception in some way, or to check for it.

This is the single most unproductive mis-feature a language could have for me. Programming is already a tedious excercise of wrangling your thoughts into an alien form the computer can understand. You want, on top of everything else, the computer to refuse to run your program at all, unless you explicitly handle every possible edge case?

I get that some people are engineers with rigid requirements. I'm an artist - I sculpt the program to produce output I'm not entirely clear on. I'm trying to make the computer to interesting, unexpected things.

Say I'm making a game. I wanna load a character sprite from an image file and draw it on the screen. Do I really need to handle all the possible ways that file could fail to load right now, before even seeing a preview of what it should look like? Hell no!

It's like having an assistant who refuses to do anything unless you specify everything! Hey assistant, get me a coffee. "I refuse to get you a coffee because you didn't specify what I should do in case the coffee machine is broken." Aargh!

2 comments

I don't quite follow. You always have to somehow handle the case the file does not load successfully. In exception languages that handling might be implicit (raise an exception and crash your program) and in "errors as values" languages you at least have to acknowledge that it could go wrong with something like `image.unwrap()` (which turns it into a program aborting panic).
One of my personal favorite examples of exception handling was a small GUI app with a single top-level exception handler at the event loop that displayed an error message and continued.

That application was extremely robust. You try and save a file and 100 different things could go wrong (network drive unavailable, file is read-only, etc) but it nicely recovered and you could see what the problem was, correct it, and re-save. One single exception handler for the whole app.

> You always have to somehow handle the case the file does not load successfully. In exception languages that handling might be implicit

I.e. you don't have to handle it.

Until you're polishing the program for a stable release, that is.

Right, in both approaches you can choose to handle the error by ignoring it and crashing. In "errors as values" languages you have to make that choice explicit by marking the line with `unwrap`. Saying that this requirement is "the single most unproductive mis-feature a language could have" is extreme hyperbole, no? Adding `unwrap`s during development to imitate implicit exceptions for fast prototyping takes no time or thought at all.

On the contrary, when you later want to polish your program for release these explicit markings make it very easy to find the points in your code where errors can occur and which you don't properly handle yet.

Okay, if there's a simple way to mark some code as "compile this even if it's wrong", it's only a minor annoyance.

But the commenter I responded to seemed to me to be wishing for a language that explicitly disallows that. Maybe I misunderstood?

> You want, on top of everything else, the computer to refuse to run your program at all, unless you explicitly handle every possible edge case?

Precisely!

Even better - let the IDE suggest to you all of the possible exceptions and when you're feeling lazy or are hacking away at a prototype, either let it add a "throws SomeException" to the method signature and make it someone else's problem up the call chain, or just add a catch all after you've handled the ones that you did want to handle!

After all, none of us can recall the hundreds of ways network calls can get screwed up, but we're pretty sure what to do at least in a subset of those, but we'd also forget about those without these reminders. Not only that, but when you're writing financial code or running your own SaaS, you'll at the very least will want your error handling code to be as bulletproof as the guarantees offered to you by your language's rigid type systems.

Then, when you've finished hacking together your logic, your instance of SonarQube or another tool could just tell you: "Hey, there are 43 places in your code where you have used logic to catch multiple exceptions" and then you could review those to decide whether further work is necessary, or whether you can add a linter ignore comment to the code explaining why you don't want to handle the edge cases, or just do so in the static code analysis tool, so all of your team members know what's up.

Alternatively, if you're just writing something for yourself, just leave it as it is, knowing that if you'll ever need to publish your code for thousands of others to use, then you probably should go back to those now very visible places and review it.

So essentially:

  /** 
    * Attempts to load a Sprite from a file. You can then use the instance to display it on screen.
    * @param file This is the file that we want to load the image from. Use relative path to "res" directory.
    * Our engine loads PNG files and technically can also load GIF files because someone hacked that functionality together in an evening. 
    * That's kind of slow though, so we should use PNGs whenever possible. See ENGINE-33452 for more details.
    * @return A Sprite instance that you can pass to the rendering logic to put it on the screen, or alternatively process the loaded image in memory.
    */
  public Sprite loadSprite(@NotNull File file) throws SpriteGenericException, FileSystemGenericException {
    try {
      return FileSystemSpriteLoader.loadPNG(file);
    } catch (ImageWrongFormatException e) {
      wrongImageFormatLogger.warn("We found a " + e.getActualFormat() + " format file: " + file.getPath(), e); // the art team should have a look at this
      if (e.getActualFormat().equals(ImageFormats.GIF)) {
        return FileSystemSpriteLoader.loadGIF(file); // TODO unoptimized call because we needed GIFs for ENGINE-33452, remove later
      } else {
        throw SpriteGenericException("We failed to load sprite from file: " + file.getPath() + " because of wrong format: " + e.getActualFormat(), e);
      }
    } catch (SpriteCorruptedException e) {
      brokenImageLogger.warn("We found a corrupted sprite in file: " + file.getPath(), e); // maybe the pipeline is broken again?
      throw SpriteGenericException("We failed to load sprite from file: " + file.getPath() + " because of image corruption", e);
    } catch (Exception e) { // TODO ENGINE-44551 handle the file system access cases later once the API is stable and we know how it'll work on Android
      throw FileSystemGenericException("We failed to load sprite from file: " + file.getPath(), e);
    }
  }
I prefer software blowing up in predictable ways as opposed to doing so unexpectedly. Even Java is vaguely close to being what i'm looking for, however unchecked exceptions simply isn't acceptable from where i stand.
If I had to write that kind of boilerplate every time I had an artistic inspiration, I'd never ship anything!

We are on far apart sides of a wide industry. I couldn't work productively in your dream language but hey, I'm happy we can have our different tools for our different needs. More power to us! :)

> let the IDE suggest to you all of the possible exceptions

So, programming without an IDE becomes untenable. I use a text editor. It feels like you're shifting language features into the IDE. What's the difference between the compiler doing it automatically vs the IDE doing it automatically?

I definitely agree that we're on the complete opposite ends of a wide spectrum of concerns and goals!

> So, programming without an IDE becomes untenable. I use a text editor. It feels like you're shifting language features into the IDE. What's the difference between the compiler doing it automatically vs the IDE doing it automatically?

I very much agree with this observation, but from the opposite side - for many development stacks and frameworks, working without an IDE feels like being a fish out of the water, since there are numerous plugins, snippets and integrations that provide intelligent suggestions, auto-completions and warnings about things that are legal within the language but are viewed as an anti-pattern.

I'd say that the difference between the two is pretty simple, just a matter of abstraction layers. Something along the lines of:

  - the business people have certain abstract goals, which they can hopefully synthesize into change requests
  - the developer has to implement these features, by thinking about everything from the high level design, to the actual code
  - the IDE takes some of the load off from the developer's shoulders, by letting them think about the problem and offering them suggestions, hints and assistance of other sorts to help in translating the requirements into code; of course, it's also useful in refactoring and maintenance as well, letting them navigate the codebase freely
  - the language server, linter, code analysis tools, plugins, AI autocomplete and anything else that the developer should want hopefully integrate within the IDE and allow using them seamlessly, to make the whole experience more streamlined
  - the compiler mostly exists as a tool to get to executable artifacts, while at the same time serving as the last line of defense against nonsensical code or illegal constructs
In essence, the IDE gives you choices and help, whereas the compiler works at a lower level and makes sure that any code (regardless of whether written by the developer with an IDE, one with a text editor or an AI plugin) is valid. In practice, however, the parts that the IDE handles are always more pleasant because of the plethora of ways to interact with it, whereas the output of a compiler oftentimes must be enhanced with additional functionality to make it more useful (for example, clicking on output to navigate to the offending code).

In my eyes, the interesting bits are where static code analysis tools and linters fit into all of this, because i think that those should be similarly closely integrated within the ecosystem of a language, instead of being seeked out separately, much like how RuboCop integrates with both Rails and JetBrains RubyMine. Our views may differ here once again, but i think that some sort of a convergence of tooling and its concerns is inevitable and as someone who uses many of the JetBrains tools (really pleasant commercial IDEs), i welcome it with open arms.

Ohh, you could have dependency management built into the IDE (probably already do, I don't know). An integrated profiler could tell you how fast a function is as soon as you write it. I'm getting funny ideas.

What if the IDE worked with a distributed function database, rather than flat text files? Where you could browse (shop?) all the code written by others, by licence, performance, etc.?

Wonder if there are any programming streams/channels I could uh, spy IDE-based development from.