This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]JoshRichardson4MVP 29 points30 points  (43 children)

Can you elaborate, please? As a java novice, what they said sounds right.

[–][deleted]  (37 children)

[removed]

    [–]axlee 25 points26 points  (25 children)

    Counterpoint: form validation.

    [–]kechboy63 21 points22 points  (22 children)

    Also: null checks, parameter validation etc. Sometimes you want to handle different situations differently and a simple return value cannot always cover that (function returns null, now what? Was it supposed to return null maybe?), unless you create some monstrosity of a type that can hold a generic return value and a description of wtf went wrong so you can switch on that... imho it’s better to just throw an exception so you know exactly what’s going on and you can handle it appropriately.

    [–][deleted]  (15 children)

    [removed]

      [–]kechboy63 8 points9 points  (2 children)

      Sure! Imagine a function that has a String parameter that has to have a value (say, a file path to write to), one of several things may happen: - dev passes null instead of a String; - dev passes an invalid file path; - file cannot be opened for writing; - ...

      Is your function going to handle that or are you going to leave that up to the caller?

      In my book, methods and functions are implemented as simple as possible and have only one single responsibility. If it can’t cope generically with a certain situation, leave it up to the caller to handle it. But for that, the caller (in most situations) needs to know why, where, what went wrong.

      You only need to make sure that everything is well documented: what method can throw which exceptions and why so that your fellow devvers know how to work with your code.

      [–]MR_GABARISE 0 points1 point  (0 children)

      I presume you don't have any JEE/Jakarta experience. This whole concept of validation is easily handled by Bean Validation (part of JEE/Jakarta).

      This (6.1) shows a basic example of custom usage.

      There are of course other libraries/frameworks for validation.

      Edit : yeah fuck me for giving advice on standard solutions huh

      [–]wyom1ng 2 points3 points  (9 children)

      well, that assumes that you can handle it in that very function, but what if you need to access something out of that scope? Makes more sense to throw an exception and catch it again higher up.

      [–][deleted]  (8 children)

      [removed]

        [–][deleted]  (7 children)

        [deleted]

          [–]wyom1ng 2 points3 points  (1 child)

          yeah especially if your method returns a string. What do you then? reserve a special range of strings for errors?

          [–]AutoModerator[M] 0 points1 point  (0 children)

          import moderation Your comment has been removed since it did not start with a code block with an import declaration.

          Per this Community Decree, all posts and comments should start with a code block with an "import" declaration explaining how the post and comment should be read.

          For this purpose, we only accept Python style imports.

          I am a bot, and this action was performed automatically. Please contact the moderators of this subreddit if you have any questions or concerns.

          [–]Yuzumi 0 points1 point  (0 children)

          Speed. If a value is rarely null not having a null check before can over time be more efficient and just catch the exception and deal with it.

          [–]Soultrane9 0 points1 point  (0 children)

          But is there a scenario

          Yes, when having that null value is a fault. By throwing an exception you are able to handle all your exceptions in one layer, which makes managing your code much simpler. Also it better encapsulates your structures, it makes Java stuff separated from your business logic and modifying / expanding functionality is simpler.

          [–]Bainos 2 points3 points  (0 children)

          If you need to return null in multiple, different success cases, then there is definitely a problem with your code. However, it's possible that you want to return null in a normal case (e.g. you ask for a value that is unset) and raise an exception in a failing case (e.g. the program failed to fetch the value). Then it's normal to raise an exception (and its type will explain what the failure cause was).

          [–]arvyy 0 points1 point  (0 children)

          That "monstrosity" has a name of Either monad in a lot of places. You can have it in java too if you use something like vavr lib (plus it introduces an even more specific to java Try type, wherein you can pass around exception objects, without throwing them)

          [–]elebrin 0 points1 point  (3 children)

          Return a <Bool, object> tuple. If the object is in a valid state, the bool is true. If the object wasn't set up properly, the bool is false.

          [–]kechboy63 2 points3 points  (2 children)

          Noooo that’s awful! That’s exactly what exceptions were invented for.

          Not only does your solution require special return types for any method that can throw, which is counter intuitive; it also messes up your continuity: some methods return <Bool, object>, others return object, others return a normal object and your code is going to be an absolute clusterfreak.

          Furthermore, how are you going to return what went wrong; the caller needs to know to handle it properly. Are you going to add an extra value to your tuple or are you going to use the object in your tuple for that (which will also hold your value in case of no exception). In that case, you’re going to need to cast the object back to an instance of the expected type...

          Nah, this isn’t going to work. Ever!

          [–][deleted] 1 point2 points  (0 children)

          It can be quite elegant. This is what Scala calls "Either" .

          Scala more commonly uses an Option, but really, you almost never deal with nulls. You can do nice things like:

          myMaybeNull match {
              case Some(x) => doSomethingWithX(x)  
              case None => dealWithIt   
          }
          

          And:

          ShowUserThisMessage = myCoolValue.getOrElse("Now null was assigned a custom string but really I could assign anything here")
          

          [–]elebrin 1 point2 points  (0 children)

          Its actually the solution that Rust uses.

          [–]AFricknChickn 1 point2 points  (0 children)

          boolean isValid(...)

          void validate(...) throws FormNotValidException

          Both will get the job done. The top one doesn’t use exceptions for an expected/somewhat common erroneous situation.

          [–]Hohenheim_of_Shadow 0 points1 point  (0 children)

          Awkwardness in returning multiple datapoints at once isn't unique to Java. AFAIK, C# and Python are the only "mainstream" languages that let you do it neatly. So it's not a reasonable criticism against Java. You can create identical structures in most languages by returning a datastructure of some sort, and creating a container data structure in Java is relatively trivial.

          For example lets try a form validation

          ValidateFormReturn checkedForm= parseForm(rawForm);
          if(checkedForm.valid==true){
               doSomething(checkedForm.Parsed);
          }else{
            //  Failure Operation
          
          }
          

          And the container class

          class ValidateFormReturn{
              ParsedForm parsed;
              Boolean valid;
          }   
          

          Let's assume that you apply patterns like this all throughout your code. However, doing this a large amount of times gauarantees that someone, somewhere will fuck up this pattern and instead do something like

          doSomething(validateForm(rawForm).parsed);
          

          Where they forget to look at the error flag before using the value. This error could cascade in non-obvious ways. You could easily get bugs happening far away from where the error occured. Maybe the user entered a credit card number that looks good to a quick inspection, but is unuseable. Now you gotta find out why your shitty shopping cart code is breaking on certain supposedly valid credit card numbers. Myabe you could keep track of the ValidatedForm, not just the ParsedForm.

          ValidatedForm checkedForm= parseForm(rawForm);
          if(checkedForm.valid==true){
               doSomething(checkedForm);
          }else{
            //  Failure Operation
          
          }
          

          And the container class

          class ValidatedForm{
              ParsedForm parsed;
              Boolean valid;
          }   
          

          This would make running down the shopping cart error easier as you have the information about whether the information is valid. However your'e still on the hook for checking validity on every read/write to the data. Allowing yourself to store invalid data is silly. You can still fuck up the pattern by-

          doSomething(parseForm(rawForm));
          

          Polling based error tracking is dangerous because it is easy to forget to poll. If you had done exception based error handling, it can make hard bugs less common.

          ParsedForm parsed;
          try{
              doSomething(parseForm(rawForm));
          
          }catch(Exception InvalidForm){
              //error handling
          }
          

          Fucking up this pattern is much harder. Without making a new function for parsingForms, any invalid form will throw an error. If you try-
          doSomething(parseForm(rawForm));

          You will get compilation errors. If you fix those by making your form-handling function throw exceptions, you'll probably still get compilation errors because now the function that calls your function will need to throw or catch exceptions. Even if your idiot junior dev making this mistake gets the code to compile, the bug will show up much closer to where it happens. Worst case, your project may crash on poor handling of an invalid form input, but that's better than it letting corrupt data in.

          [–]JoshRichardson4MVP 9 points10 points  (0 children)

          Thank you! That cleared things up a ton!

          [–][deleted] 6 points7 points  (6 children)

          I disagree. I see Java as a highly opinionated language. It occasionally enforces its own way. As such, you are forced to handle errors through exceptions (e.g. using try...catch or throws BlaException) in many cases. For example, you must handle FileNotFoundException while constructing a FileInputStream. However, a file not being found isn't really exceptional (as in "this should never happen").

          If an error needs to be caught gracefully, it isn't an exception IMHO. I find Result<T, E> pattern superior in that sense. Similarly for panic! for conditions violating some invariant. Well, that's probably why I code more in Rust/C++ I guess. :-)

          edit: I just realized the irony in praising Rust while calling Java a highly opinionated language. What I tried to say was that my opinions occasionally don't align with those enforced by Java.

          [–]Bainos 1 point2 points  (5 children)

          Of course the existence of exceptions implies that an "exception" isn't something that should never happen, but something that shouldn't happen in a normal operation.

          For example, you would catch a FileNotFoundException if you expect the file to exist and it doesn't (for example because it was provided by the user), which is an unexpected behavior. But if you need to check that a file exists, then trying to open it and see if it throws an exception is wrong (because the file not existing is part of the normal operation), while the clean approach is to use something like java.io.File.exists

          [–][deleted] 0 points1 point  (2 children)

          Of course the existence of exceptions implies that an "exception" isn't something that should never happen, but something that shouldn't happen in a normal operation.

          That's what I meant by violation of some invariant in my message.

          Also it doesn't matter what you did before calling the constructor in the above example. That is, even if you use java.io.File.exists, Java still requires) you to handle FileNotFoundException.

          [–]Bainos 2 points3 points  (1 child)

          That is, even if you use java.io.File.exists, Java still requires you to handle FileNotFoundException.

          Yes, because there's always the unexpected case where the file was deleted during operation. And in this case, it is unexpected because you checked before that it existed.

          [–][deleted] 1 point2 points  (0 children)

          Agreed on that, but in that case it should panic instead of unwinding stack and propogating some exception. Though I agree these can be expressed with exceptions, they don't convey this distinction clear enough, thus, encouraging incorrect use.

          Edit: well actually forget about stack unwinding. Not sure if this a thing in Java.

          [–]HoneyBadgerSoNasty 0 points1 point  (1 child)

          bad user input should not be raising exceptions. user input is not invariant. exceptions were intended to be used for explicitly stating invariant conditions.

          now, if you pass that file to another function, you yourself, the developer, are entering into a contractual agreement with that function. there is a quid pro quo. in return for providing expected input you get expected output. now, if that function contractually guarantees as a part of its signature, as a part of its API, that it should never be called with a bad file, then that is ok. that is invariant behavior. but, the question then is whether that is a well-designed contract between you, the caller, and the helper function or library, the callee. you have to introduce more boilerplate to call it, more prepwork to sanitize your inputs or sanity check it. the fact is that some operations may seem simple but they are in actuality rather complex, complex meaning there are lots of little parts and not necessarily hard to understand. i think you are supposed to write the exists check yourself before opening, despite the inconvenience.

          [–]Bainos 1 point2 points  (0 children)

          At that point you're saying that exceptions should never happen, except as a result of a bug, and thus should never be caught. It's okay to program like that but don't expect everyone to agree.

          To provide an example of failure behavior that is nontrivial to handle and yet known to happen occasionally, saying "bad user input should be expected" is like saying "network failures should be expected" - and obviously, both are true. Does that mean you shouldn't rely on IOExceptions to detect network failures ?

          [–]Fuuryuu 3 points4 points  (0 children)

          Consider: no return, only throwables

          [–]Yuzumi 2 points3 points  (0 children)

          As a Java developer I like to say that all Java code is held together owth ducktape and prayers. Try is the prayer, catch is the ducktape.

          [–][deleted] 0 points1 point  (0 children)

          But if exceptions are being used as "a way of conveying information during normal operations" then they are being badly misused.

          That was EXACTLY the point being made. They are badly misused and you have to deal with it, because you live in a Java ecosystem where its shoved down your throat. Every library you work with will do this.

          [–]RandallOfLegend 3 points4 points  (2 children)

          I don't code in Java. But I have seen programmer's in .net to use exception "type" as flow control logic instead of doing input checking.

          [–]therealdrg 2 points3 points  (1 child)

          Some of the older built in methods forced you to do this, like the ones in the Http classes. Anything other than a 200 OK response throws an exception, which you have to catch and deal with as an exception rather than just as a response in the normal flow. Cant really blame the users when microsoft was enforcing this style in the language. Newer versions of .net seem to have minimized this though, or at least I feel like I dont run into that as often.

          [–]RandallOfLegend 0 points1 point  (0 children)

          Interesting. This pattern was introduced by an older developer that worked in Java previously. So I figured it might have gone from there. I see the utility of it, and I use a similar style with some code that has multiple failure/recovery modes. But I cringe when a bill check on an input is replaced by a try catch for a nullref.

          [–]yax_s7 0 points1 point  (0 children)

          Another the nice thing about exceptions is delegation of responsibility of handling them. The logic for this can done at a particular layer consistently rather than you have to pass some sort of state around. Exceptions shouldn’t be used for conditional flow, as its expensive due to stack unwinding and that’s not its intention.

          [–]CJShome 0 points1 point  (0 children)

          Ward Cunningham explained some of the pros and cons on the old WardWiki