-
Notifications
You must be signed in to change notification settings - Fork 20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Working with exceptions is too easy to get wrong #591
Comments
How about Maybe there could also be a exception MyCustomExn of innerException : exn * message : string
try
1 / 0
with e ->
raiseChained<MyCustomExn> e "Something went wrong" which would create, and then raise, a new instance of MyCustomExn with InnerException pointing to the divide-by-zero exception, and message "Something went wrong". I'd like to see some way of writing Wait, I just had an idea. If I was writing in Lisp, this is the point where I'd be reaching for macros, to reach into the let raiseChained innerExn ([<ReflectedDefinition>]outerExn : exn * expr : Expr<exn>) =
// ... Then try
1 / 0
with e ->
raiseChained e MyCustomExn("Something went wrong") Thoughts? |
@rmunn Thanks for the suggestions. Ok let me go into this one by one:
Thinking about this as a whole, I'll probably add the |
Modified my initial post and changed it to "L" because of different code-generation of |
You could avoid it with
and if you wanted to specify the type of exception, you could replace |
open System
open Microsoft.FSharp.Core.Printf
let rethrowa f fmt =
ksprintf (fun msg -> raise <| f msg) fmt
let rethrowb f inner fmt =
ksprintf (fun msg -> raise <| f(msg,inner)) fmt
type MyCustomExn = Exception
let param = "some text"
let inner = Exception("foo")
rethrowa (fun msg -> MyCustomExn(msg, inner)) "some message using %s" param
rethrowb MyCustomExn inner "some message using %s" param that doesn't look too bad. It might look a bit awkward when the exception has custom parameters (ie not only |
I like the warnings idea, and think that they are definitely doable as a small-to-medium effort. What kind of messages did you have in mind for the antipatterns you described above? |
We first need to discuss when we expect the warning as we could make this as "intelligent" as we want:
The message could be
|
Just wanted to say that
works really nice in practice because every exception should have that constructor. So, I'm still missing a good idea how we could glue everything together nicely and not have half of it feel wrong. |
Just some ideas about the
type Record =
| Sub1
| Sub2
exception SimpleException
exception MyException of Record
exception MyException2 of test:string * r:Record
let inner = exn("Inner Exception")
try
createSimpleExn MyException Sub1 inner "TestString" // (string * exn -> 'exn) -> exn -> string -> 'exn
createSimpleExnf MyException Sub1 inner "TestString %s" "format" // (string * exn -> 'exn) -> exn -> format...
createExn MyException Sub1 inner "TestString"
createExn MyException2 ("test", Sub1) inner "TestString" // ('a -> 'exn) -> 'a -> exn -> string -> 'exn
createExnf MyException2 ("test", Sub1) inner "TestString %s" "format"
raiseExnf MyException2 ("test", Sub1) inner "TestString %s" "format" // ('a -> 'exn) -> 'a -> exn -> format ...
raiseSimpleExn exn inner "TestString" // (string*exn -> 'exn) -> exn -> string -> 't
raiseSimpleExnf exn inner "TestString %s" "format" // (string*exn -> 'exn) -> exn -> string -> 't
raise <| MyException(Sub1)
with
| MyException(Sub1) -> ... Imho we need a function for creating an exception object and ideally a convenience function to So In a perfect world we would not need the We probably would extend the warning I suggested above with hints to those new primitives when we detect that an F# |
@matthid Did you consider supporting settable properties in exception construction? e.g.
(given your declarations above) That feels more orthogonal from a language perspective? All settable properties on the |
@dsyme While that would work technically, I feel like the simple default use case needs to be simplified a lot. It should not involve parenthesis
It definitely would be the correct first step to make it POSSIBLE to do the right thing (which your suggestion enables). I'd prefer a solution which PUSHES you into doing the right thing. And bonus points if it looks as nice as |
@matthid OK, I see. I think that's a hard goal to achieve through an API which would itself basically be optional to use. The warning you recommend would help though. |
Is there any subset where a PR has a chance of being accepted? @dsyme regarding your last suggestion I feel like |
@matthid let me try to simplify this. |
@charlesroddie Indeed: you shouldn't throw or catch plain "Exception"s according to .NET design guidelines. So, in fact, you shouldn't use |
Yeah, We also adopt a similar guideline in the F# docs: https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/conventions#use-exceptions-when-errors-cannot-be-represented-with-types |
I think in practice people use |
Yes. But I don't see how that changes anything:
In general I feel like using
Yes maybe, but I'm not sure I agree on the exception part of this. The guidelines makes sure writer and users of code agree on how exceptions are used. As we have C# users using F# libraries and the other way around we should agree on exception usage. Otherwise F# libraries force C# authors to catch general exceptions (
Yes they most likely should be all revisited. Same goes for the compiler IMHO. But that is out of scope for this suggestion. Maybe we should warn on every instance of |
I don't think that the guidelines should reflect what people do in practice, but rather what people probably should do in practice. As an example, many F# developers avoid exceptions like the plague, but that doesn't mean it isn't valuable to recommend using them (and the diagnostic data they provide) in exceptional circumstances. |
Yes, I think in this case the guidance is too strong. Using So the guidance should probably be refined to be more nuanced. |
Maybe it looks like the suggestion has shifted from "rethrowing" and wrong exception handling to not using I think I'll add that point to the initial post. |
P(need to query an exception to work out what to do) = P(exception) * P(handle exception rather than crashing | exception) * P(need to query the exception to work out what to do | handling exception), which is a product of three small numbers, so is very small. So OTOH making it easier to use System.SomeSpecificException instead of System.Exception and making inner exceptions easier to include does make sense. |
@dsyme I think the docs are pretty clear about it: https://docs.microsoft.com/en-us/dotnet/fsharp/style-guide/conventions#use-exceptions-when-errors-cannot-be-represented-with-types Since the recommendation overall is to follow .NET guidelines for exceptions, and |
The distinction is roughly between script programming, app programming and component programming. In (all) script and (most) app programming, The .NET guidelines were written from the "enterprise component" perspective in the great era of enterprise coding and do not carry over completely to either F# or C# script and app programming. If you're doing script or (some) app programming in C# you should not go around defining new exception types - though you should use an existing refined exception type where it makes sense.
It wasn't not really a holdover - we embraced it as convenient, and so its inclusion was intentional ( The I don't disagree with the OP for component programming, but have misgivings about applying component programming rules to script and app programming. There are other examples of this distinction - for example consider warning 1182 (unused variables), which is not on by default for script programming (and I wouldn't want it on) - though I'd be happy to have it on in our templates for component (libraries), and perhaps also apps. |
The distinction between short-lives scripts and long-lived applications is probably fine, but I honestly don't see too much value in defining best practices for this w.r.t error-handling. If the guideline is, "if you're not doing something critical then you don't need to think too hard about error handling, then that guideline is probably better left unstated. "Do what's convenient if correctness and maintainability doesn't matter" isn't very helpful.
Guidelines should not apply for unpolished/in-progress components. The point of them is that you follow this sort of stuff when you're looking to polish components. |
A polished script or polished app should follow coding style guidelines (e.g. formatted properly, but can use A polished component (i.e. library, nuget package) should follow both coding and style component guidelines. So this is a component guideline. For example, I am fine with a polished TicTacToe or FabulousContacts app using "failwith" for unexpected error conditions or unreachable code in utility code. I'm not ok with 53 examples of "failwith" in FSharp.Core, but I'd take them on a case by case basis and apply the component recommendations. For unreachable code I'd still expect a few as we have no other recommendation for that. |
To get back to the concrete suggestions
|
@dsyme, does this mean it's approved in principle? If so, could you mark it such, so that someone can start making an RFC? Or should we discuss further until more details are sieved out? |
Yes, the above portions are approved-in-principle. The detail may be contentious but please go ahead with an RFC for those bits |
Now that class constructors can be fully treated as first-class functions (ie we can pipe into them and so on), we could even treat |
I'd like to refine the suggestions while the discussion emerges so I'll start with the reasoning:
with _ ->
orwith exn ->
, even the compiler and hereThe problem is that code like this makes debugging and finding errors extremely difficult when some parts are moving and different (at the time of writing unexpected) errors are thrown. Basically this style catches exceptions you didn't intend to catch.
with _ -> None
, often used in a collectfailwith
is wrong in most locations: example. In .NET you should always include the reason for the error in theInner
exception field. So the correct code is most of the timewith e -> raise <| MyException("string", e)
which is both a lot more ugly and harder to write thanwith _ -> failwith "string"
. Again not following this makes finding the root cause unnecessary complex and difficult for the developer.failwith
shouldn't be used as it throws anew System.Exception(msg)
which shouldn't be done accoding to the .NET Guidelines. Its usage basically forces callers to catch general exceptions, which again you shoudn't (see first point).exception
keyword makes it practically impossible to follow best practices in .NET exception handling.I propose
System.Exception
handler.raise <| MyException(..., e)
. Currently I don't have a good idea maybefailwithf "text" , exn
to allow setting an "InnerException" when usingfailwithf
raisef<MyException> "text", exn
to allow raising a custom exception via thestring * exn
constructor.failwithe : exn -> string -> 't
@rmunnfailwithe : exn -> StringFormat<'a,_> -> 'a
@piasterethrowf : (string -> 'a -> #exn) -> 'a -> StringFormat<'c,_> -> 'c
@piasterethrowf : (string -> #exn) -> StringFormat<'c,_> -> 'c
@piasteexception
keyword in a way to allow setting theMessage
andInnerException
properties (ie there should be constructors calling the base constructors and language primitives to use them, for examplecreateExn<'e> : exn -> msg -> 'e
).Message
by using override.To be honest those are not very well thought through...
The existing way of approaching this problem in F# is doing it the wrong way.
Pros and Cons
The advantages of making this adjustment to F# are
The disadvantages of making this adjustment to F# are
Extra information
Estimated cost (XS, S, M, L, XL, XXL): L
Related suggestions: I have not found anything here on github
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
I'd like it to be non-breaking if possible. I don't think the above are actually breaking
/discuss
The text was updated successfully, but these errors were encountered: