The coding conventions say very little about exception handling in MediaWiki. In fact, the only relevant paragraph is:
Exceptions that indicate programming errors should be one of the exceptions that ship with PHP or a more specific subclass, while exceptions that indicate errors that are relevant to the end user should be an ErrorPageError or one of its subclasses.
Effectively, this means that exception handling is done differently in different areas or codebases, depending on who wrote or approved certain code changes. In particular, I would like to focus on two aspects:
- What exception classes should be thrown
- When and how to use @throws annotations
Over the past years, I've seen many commits introducing code that throws or handles exceptions, and the general consensus amongst developers (which actually goes beyond MediaWiki) is that:
- You should never throw the base Exception class; instead, either use another exception available in the SPL, or define a new exception type.[1]
- SPL exceptions (like InvalidArgumentException or UnexpectedValueException) are generally not meant to be caught.
- @throws annotations should be reserved for checked exceptions, that is, exceptions that callers should either catch or document in their own doc comment.
Additionally, there seems to be some uncertainty as to when MWException should be used (either directly or as a base class) [2], whether it should be caught, etc. All in all, this produced inconsistent code based on varying assumptions, and such code cannot be validated statically (at least for what concerns exception handling).
Therefore, I am proposing that:
- We agree on how exception handling should work in MediaWiki, potentially using the rules above (de facto, the status quo) as a starting point.
- The new rules should be documented in the coding conventions, in a dedicated section about exception handling.
- These rules should be enforced by linters (PHPCS) or static analysis tools (phan).
- Possibly, the purpose of MWException should be documented and clarified, both in the class comment and in the coding conventions.
In particular, I think the linters should do the following:
- Flag code that throws Exception (this can be done with PHPCS)
- Ensure that if a callee has @throws annotations, the caller either catches those exceptions, or documents them in its own doc comment (phan can do this with the warn_about_undocumented_exceptions_thrown_by_invoked_functions config option)
- Possibly: flag code that throws a checked exception but does not document it in the doc comment. Phan can do this with the warn_about_undocumented_throw_statements option; the problem is that phan doesn't know which exceptions are (un)checked. The config option exception_classes_with_optional_throws_phpdoc can be used to specify that, but I'm not sure if the current exception hierarchy distinguishes checked vs unchecked exceptions clearly enough for this.
I imagine a lot of code might have to be cleaned up, so the proposal is that we enable the new rules by default, but disable them on repos where they fail. They can then be re-enabled incrementally as noncompliant code is rewritten. Related: T240672: Clean up unchecked exceptions.
[1] A good question here would be if there are other exception classes that should not be thrown directly. Opinions about this seem to vary.
[2] As a matter of fact, the class name does not explain its purpose. The doc comment of that class only says "MediaWiki exception". I would say it's not incredibly helpful, is it?