Internal (bug, e.g. divide by zero or missing function definition in a dynamic language) vs external (out-of-your-control corner case, like file not found) is an important axis.
But another axis is "finality":
1. do you just want to never crash (return code)
2. sometimes crash but have the ability to deal with the problem up the callchain (exceptions in most languages)
3. sometimes crash but be able to fix the problem and continue, at the point the error occured -- not up the call stack (restart-case etc. in common lisp)
4. sometimes crash, but have a supervisor hierarchy make an informed decision if and how to restart you and things in your dependency tree (erlang)
5. crash (panic, assert, exit) and maybe have some less sophisticated but probably very complicated mechanism take care of restarting/replacing you (systemd, kubernetes etc.)
This axis may not be completely orthogonal, but probably mostly is. For example resumable conditions are nice in common lisp both to deal with external stuff (no space left on device? ask user to abort or free some up, and just resume download instead of erroring out as webbrowsers do) but also to just fix problems as you run into them and continue your computation during development, including calling a function you did not define – you can just define it and resume the call to it.
Sadly, the choices in most languages for this second axis are much more constrained. Erlang's supervision trees and common lisp's resumable exceptions in particular seem very useful in many scenarios but nothing else has them (well, elixir has everything erlang has, but it's still the same VM/ecosystem).
But another axis is "finality":
1. do you just want to never crash (return code)
2. sometimes crash but have the ability to deal with the problem up the callchain (exceptions in most languages)
3. sometimes crash but be able to fix the problem and continue, at the point the error occured -- not up the call stack (restart-case etc. in common lisp)
4. sometimes crash, but have a supervisor hierarchy make an informed decision if and how to restart you and things in your dependency tree (erlang)
5. crash (panic, assert, exit) and maybe have some less sophisticated but probably very complicated mechanism take care of restarting/replacing you (systemd, kubernetes etc.)
This axis may not be completely orthogonal, but probably mostly is. For example resumable conditions are nice in common lisp both to deal with external stuff (no space left on device? ask user to abort or free some up, and just resume download instead of erroring out as webbrowsers do) but also to just fix problems as you run into them and continue your computation during development, including calling a function you did not define – you can just define it and resume the call to it.
Sadly, the choices in most languages for this second axis are much more constrained. Erlang's supervision trees and common lisp's resumable exceptions in particular seem very useful in many scenarios but nothing else has them (well, elixir has everything erlang has, but it's still the same VM/ecosystem).