David Schwartz 426 Posted August 5, 2019 (edited) What's the easiest way you know of to test predicate conditions of input parameters on methods and display a meaningful error message when one fails and skipping the rest, without using exceptions? Common situations include: * an input value is NIL or not * an input string is empty or not * an input numeric is < 0, = 0, or > 0 * an input object has a property like the above that includes that the object's reference pointer is valid I'm curious if there's a way to have a list of one-liners that are easy to read and aren't part of a huge nested if..then statement filled with and's or or's and terminated with a begin ShowMessage(...); Exit; end; Edited August 5, 2019 by David Schwartz Share this post Link to post
David Heffernan 2345 Posted August 5, 2019 Best is to use exceptions of course 1 Share this post Link to post
David Schwartz 426 Posted August 5, 2019 (edited) 48 minutes ago, David Heffernan said: Best is to use exceptions of course Well, aside from a general sense that exceptions shouldn't be used for flow control ... Anyway, I'm looking for some examples as well. This is not a topic that is very widely discussed, although for lots of projects I've worked on, they've devised some kind of consistent approach, usually NOT based on exceptions. But many haven't, so you can see personality differences in the code depending whose favorite method for vetting incoming parameters was used. This isn't very Delphi-specific, so discussions about it in other languages would be fun to read, too. Edited August 5, 2019 by David Schwartz Share this post Link to post
Stefan Glienke 2002 Posted August 6, 2019 Ok, if we are talking other languages: best is to not be even able to compile the code or get warnings when the static code analysis cannot make sure that these conditions are met - for example using non nullable reference types. Share this post Link to post
David Heffernan 2345 Posted August 6, 2019 This isn't using exceptions for control flow. It's using exceptions to deal with invalid parameters. If you don't use exceptions, and you show a message dialog to the user then what is code going to do next? How are you going to avoid following the normal execution path. You can exit this function. But what about the caller? And its caller? And its caller? And what about when you run your code through your program's API rather than interactively. Pretty debilitating to show a dialog to an API user. Share this post Link to post
David Schwartz 426 Posted August 6, 2019 33 minutes ago, David Heffernan said: This isn't using exceptions for control flow. It's using exceptions to deal with invalid parameters. If you don't use exceptions, and you show a message dialog to the user then what is code going to do next? How are you going to avoid following the normal execution path. You can exit this function. But what about the caller? And its caller? And its caller? And what about when you run your code through your program's API rather than interactively. Pretty debilitating to show a dialog to an API user. Ahh, well ... I'm working on a form-based app, and the code I'm staring at isn't of the nature you're describing. But I see your point, for sure. Again, I'm looking for some examples, because the few I've seen don't take things like this into consideration. Historically speaking, these things get lumped into "error logging" mechanisms. If you're working with a server that has no display, then that's a valid perspective. But not so much in a form-based app -- you want to tell the user to fix the problem so processing can continue. Even in situations where the client-side code ends up passing improperly filtered data to the back-end, there needs to be a way to a message from the back-end that the front-end can then use to alert the user so the problem can be corrected. I don't see this much in desktop apps, even using DB servers. But boy do I see it a lot in web-based scenarios where a simple data entry error in one field causes the entire form to be reset and all of the previous data to be lost. (I was trying to enter some data into a site that asks for some work history, starting with the most recent / current thing. I added a couple then realized I forgot my "current" status. There's no way to enter something anywhere but at the end. After several attempts at moving things down, I just deleted everything and started over. Apparently there's a bug where if you check the box that says you're "currently employed" there, the back-end won't accept that, and it deletes the entire (first) entry, which is why i couldn't move things down. After deleting everything, it just left me with an empty list and no error message.) I see this same kind of problem repeatedly in web-based scenarios, and no consistent way of handling it. All these fancy frameworks and nobody seems to have a well thought-out way of handing these kinds of exceptions. Share this post Link to post
David Schwartz 426 Posted August 6, 2019 2 hours ago, Stefan Glienke said: Ok, if we are talking other languages: best is to not be even able to compile the code or get warnings when the static code analysis cannot make sure that these conditions are met - for example using non nullable reference types. No, I'm thinking about run-time errors here. Like, you cannot proceed without entering something in a Name field. You could say it's "required field" handling, but the form follows one set of rules, the back-end might follow one or two others, and they're all independent of each other. If you decide to change a field from required to options, or vice versa, you have to implement that logic in several different places, and it's not always obvious where they are. And it's hard to test for. Share this post Link to post
David Heffernan 2345 Posted August 6, 2019 It still says exceptions to me. But you say that you don't want to use exceptions. Can you explain why you don't want to use exceptions? Share this post Link to post
Stefan Glienke 2002 Posted August 6, 2019 To get a sophisticated answer you need to actually describe your situation properly and not just say "invalid values" - invalid in what context? Restricting UI from entering any invalid values can go from controls that don't even allow it, show a marker that their value is wrong, not allowing clicking ok/next to showing an "following fields have invalid values..." dialog. Declaring guard clauses in code for functions and methods that restrict passing any invalid input is a different thing and in that case I would always go with exceptions of a certain kind (EInvalidArgument or similar) either handwritten or by using some guard clause helper. If the programming language allows I would push this further by restricting the parameter types to only allow what is valid and for instance use decicated domain types instead of for example type string for an IP address but a dedicated TIPAddress type. 1 Share this post Link to post
DiGi 14 Posted August 6, 2019 Are you asking for something like https://fluentvalidation.net/? I don't know about similar Delphi framework Share this post Link to post
Stefan Glienke 2002 Posted August 6, 2019 5 minutes ago, DiGi said: Are you asking for something like https://fluentvalidation.net/? I don't know about similar Delphi framework Any approach like this in Delphi would be impossible or suck (because it would need to be stringly typed) because Delphi does not have lambda expressions and generic extension methods. 1 Share this post Link to post
PeterBelow 238 Posted August 6, 2019 15 hours ago, David Schwartz said: Well, aside from a general sense that exceptions shouldn't be used for flow control ... Anyway, I'm looking for some examples as well. This is not a topic that is very widely discussed, although for lots of projects I've worked on, they've devised some kind of consistent approach, usually NOT based on exceptions. But many haven't, so you can see personality differences in the code depending whose favorite method for vetting incoming parameters was used. This isn't very Delphi-specific, so discussions about it in other languages would be fun to read, too. I usually test for pre and postconditions like this: procedure TBlobDB.CreateNewDatabase(aDBStream, aIndexStream: TStream; aTakeStreamOwnership: boolean); const Procname = 'TBlobDB.CreateNewDatabase'; begin if not Assigned(aDBStream) then raise EParameterCannotBeNil.Create(Procname,'aDBStream'); if not Assigned(aIndexStream) then raise EParameterCannotBeNil.Create(Procname,'aIndexStream'); This is for errors that are supposed to be found and fixed during the testing phase or (for library code) in unit tests. For user input the input is validated before it is used by the program, if this is at all feasable. The end user should never see an exception coming from parameter validation at a lower level, in my opinion, since the UI context may not be evident at that point and it may be unclear to the user what he did wrong. 3 Share this post Link to post
David Heffernan 2345 Posted August 6, 2019 6 hours ago, PeterBelow said: I usually test for pre and postconditions like this: procedure TBlobDB.CreateNewDatabase(aDBStream, aIndexStream: TStream; aTakeStreamOwnership: boolean); const Procname = 'TBlobDB.CreateNewDatabase'; begin if not Assigned(aDBStream) then raise EParameterCannotBeNil.Create(Procname,'aDBStream'); if not Assigned(aIndexStream) then raise EParameterCannotBeNil.Create(Procname,'aIndexStream'); This is for errors that are supposed to be found and fixed during the testing phase or (for library code) in unit tests. For user input the input is validated before it is used by the program, if this is at all feasable. The end user should never see an exception coming from parameter validation at a lower level, in my opinion, since the UI context may not be evident at that point and it may be unclear to the user what he did wrong. Why even bother writing those checks? An exception will be raised as soon as you attempt to use the nil instances. Share this post Link to post
Guest Posted August 6, 2019 6 minutes ago, David Heffernan said: Why even bother writing those checks? An exception will be raised as soon as you attempt to use the nil instances. Because of fail fast Share this post Link to post
David Heffernan 2345 Posted August 6, 2019 24 minutes ago, Schokohase said: Because of fail fast Reasonable at certain interfaces between modules, but not in every single function. Share this post Link to post
Georgge Bakh 29 Posted August 6, 2019 On 8/5/2019 at 11:14 PM, David Schwartz said: What's the easiest way you know of to test predicate conditions of input parameters on methods and display a meaningful error message when one fails and skipping the rest, without using exceptions? Common situations include: * an input value is NIL or not * an input string is empty or not * an input numeric is < 0, = 0, or > 0 * an input object has a property like the above that includes that the object's reference pointer is valid I'm curious if there's a way to have a list of one-liners that are easy to read and aren't part of a huge nested if..then statement filled with and's or or's and terminated with a begin ShowMessage(...); Exit; end; With these requirements the solution may be something like: Validator .Check(value <> nil, 'Value is nil!') .Check(str <> '', 'String is empty!') .Check(Value.Num > 0, 'Value.Number <= 0!'); Check() receives a boolean to check and string message to complain. If boolean is true returns Self. If false - other instance whose Check() method just returns Self on any input. Share this post Link to post
David Schwartz 426 Posted August 7, 2019 (edited) 3 hours ago, Georgge Bakh said: With these requirements the solution may be something like: Validator .Check(value <> nil, 'Value is nil!') .Check(str <> '', 'String is empty!') .Check(Value.Num > 0, 'Value.Number <= 0!'); Check() receives a boolean to check and string message to complain. If boolean is true returns Self. If false - other instance whose Check() method just returns Self on any input. Ahh, and example! Thanks! So how do you bail out of the procedure if a check fails? Does this throw an exception? Or is another test needed? @David Heffernan I'm actually fairly agnostic about Exceptions, It's just that they can be a PITA at times. Edited August 7, 2019 by David Schwartz Share this post Link to post
David Schwartz 426 Posted August 7, 2019 You guys are all really great theoreticians. I'd really much rather see a variety of examples than debate theory. That's because I see very little written about this topic anywhere. Share this post Link to post
David Heffernan 2345 Posted August 7, 2019 Examples aren't much use without understanding the rationale behind them. If exceptions are a PITA you must be doing something wrong. It's error handling without exceptions that is a PITA. Share this post Link to post
Guest Posted August 7, 2019 5 hours ago, David Schwartz said: You guys are all really great theoreticians. I'd really much rather see a variety of examples than debate theory. That's because I see very little written about this topic anywhere. We can only be theoretically/abstract because you asked a theoretically/abstract question: 21 hours ago, Stefan Glienke said: To get a sophisticated answer you need to actually describe your situation properly and not just say "invalid values" - invalid in what context? Share this post Link to post
Uwe Raabe 2057 Posted August 7, 2019 12 hours ago, David Heffernan said: Why even bother writing those checks? An exception will be raised as soon as you attempt to use the nil instances. Because I can set up my IDE to skip EParameterCannotBeNil exceptions in the debugger, while still catching other nil pointers. Share this post Link to post
Микола Петрівський 10 Posted August 7, 2019 11 hours ago, Georgge Bakh said: With these requirements the solution may be something like: Validator .Check(value <> nil, 'Value is nil!') .Check(str <> '', 'String is empty!') .Check(Value.Num > 0, 'Value.Number <= 0!'); Check() receives a boolean to check and string message to complain. If boolean is true returns Self. If false - other instance whose Check() method just returns Self on any input. It is almost the same as Assert(value <> nil, 'Value is nil!'); Assert(str <> '', 'String is empty!'); Assert(Value.Num > 0, 'Value.Number <= 0!'); Share this post Link to post
David Heffernan 2345 Posted August 7, 2019 54 minutes ago, Uwe Raabe said: Because I can set up my IDE to skip EParameterCannotBeNil exceptions in the debugger, while still catching other nil pointers. Every function you write that accepts a reference type, you check that it is assigned on entry to the function? Share this post Link to post
Guest Posted August 7, 2019 16 minutes ago, David Heffernan said: Every function you write that accepts a reference type, you check that it is assigned on entry to the function? If it is public available? - Yes. If it is only private - No (or very rare). Share this post Link to post
Stefan Glienke 2002 Posted August 7, 2019 (edited) 13 hours ago, David Heffernan said: Why even bother writing those checks? An exception will be raised as soon as you attempt to use the nil instances. Not true - this ain't .NET or Java that raise an NPE as soon as you reference any member - it can very well call non virtual methods and then eventually raise some AV which you then need to decipher (everyone knows what an access violation at 0x0071f2a7: read of address 0x000000f7 means, right?) Edited August 7, 2019 by Stefan Glienke Share this post Link to post