PSA: dnSpy is a terrifyingly good .NET decompiler



  • @dkf said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    No, it doesn't. That information isn't collected now, and is onerous to collect.

    So don't collect it, just don't ask me to write it on every function like Java does.

    edit: Also, you're lying, because if the compiler wasn't collecting it on Java, it wouldn't be able to give me an error saying I did'nt declare it.


  • Discourse touched me in a no-no place

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    if the compiler wasn't collecting it on Java

    It doesn't collect anything that's a subclass of Error or RuntimeException; every piece of code is assumed by the language to able to throw those.


  • Banned

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    Do you do that because you've been burnt too many times, or do you really think it's wrong to limit how many kinds of errors a given function might raise?

    I don't do anything, I'm just saying that throws clause never helped me when I used java, and I never missed it when using anything else.

    But is it because you always catch Exception and never its subclass out of habit, or because you think there's actually zero value in knowing which particular errors a given function might encounter? If everyone made all their exceptions make sense and unchecked exceptions didn't exist, would you still think throws clauses are useless?

    Imagine doing that without an IDE, you would end in a stupid cycle of compile, look what exception it complains about and updating the throws clause.

    It's only stupid if you don't ever want to handle those exceptions in sensible way and don't care what happens when error is encountered. Which is bad code. Good code cares about errors, and compiler message "this unchecked exception gets unhandled" doesn't mean "you forgot throws clause" - it means "you forgot to handle this error and you better fix it!"


  • Banned

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    edit: Also, you're lying, because if the compiler wasn't collecting it on Java, it wouldn't be able to give me an error saying I did'nt declare it.

    It doesn't collect info about thrown exceptions. It only collects info about throws clauses. And the programmer is forced to do that explicitly to make sure they didn't forget about any error condition when writing code.


  • kills Dumbledore

    @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    But is it because you always catch Exception and never its subclass out of habit, or because you think there's actually zero value in knowing which particular errors a given function might encounter?

    catch(Exception e)
    {
        if (e is NullReferenceException)
        {
            //handle null reference
        }
        else if (e is FileNotFoundException)
        {
            return FILE_NOT_FOUND;
        }
        else if (e is FooException)
        {
            HandleFoo(((FooException)e).bar);
        }
        else
        {
            HACF();
        }
    }
    


  • @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    But is it because you always catch Exception and never its subclass out of habit, or because you think there's actually zero value in knowing which particular errors a given function might encounter?

    I didn't say I always catch Exception. I think the knowledge of what particular errors a given function might encounter provides doesn't give enough value to pay for the "throws" clause.



  • @gaska @Jaloopa I used to have things like this on code that respond to a user action:

    catch (UserDoneSomethingWrongException e) {
        DisplayMessage(e.YoureDoingItWrongMessage);
    }
    catch (Exception err) {
        DisplayMessage("Something didn't work. Maybe try it again in a few minutes, or call our support or whatever");
        LogStackTrace(err);
    }
    

    and UserDoneSomethingWrongException would be a base class for any exception that should result in a message back for the user


  • Banned

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    But is it because you always catch Exception and never its subclass out of habit, or because you think there's actually zero value in knowing which particular errors a given function might encounter?

    I didn't say I always catch Exception. I think the knowledge of what particular errors a given function might encounter provides doesn't give enough value to pay for the "throws" clause.

    Just to make it clear - if the throws clauses were less obnoxious (I don't know how; just assume for a moment they'd be), would you be in support of checked exceptions or not? Do you think the value of knowing the possible errors is significant, or within the margin of error from zero?


  • ♿ (Parody)

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    I don't do anything, I'm just saying that throws clause never helped me when I used java, and I never missed it when using anything else. I don't approve of a compiler trying to impose those things on me. The compiler already has all the means to know what exceptions a function throws, I don't need to say it for it.

    I used to hate checked exceptions. I eventually realized that I shouldn't view it as the compiler imposing on me but the code itself. Just like the return type of a method does. Except this was for error handling. I realized my biggest complaint about them was :kneeling_warthog: and that I was fighting against myself by disliking them.


  • Discourse touched me in a no-no place

    All this reminds me of some code I wrote recently…

    try {
    	try {
    		method.invoke(obj);
    	} catch (InvocationTargetException innerException) {
    		// Unwrap the inner exception
    		throw innerException.getTargetException();
    	}
    } catch (RuntimeException | Error
    		| MyApplicationException realException) {
    	// These are the real things that can be thrown
    	throw realException;
    } catch (Throwable badException) {
    	// Should be unreachable
    	throw new RuntimeException("bad call", badException);
    }
    

    Unfortunately, I needed to do it that way because of other tricky bits (not in scope, relating to annotation handling). I guess I could have written some sort of compiler enhancement to let it check things for me correctly (or to do code generation) but :kneeling_warthog:



  • @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    Just to make it clear - if the throws clauses were less obnoxious (I don't know how; just assume for a moment they'd be), would you be in support of checked exceptions or not? Do you think the value of knowing the possible errors is significant, or within the margin of error from zero?

    Dunno, that is just how it felt for me the way things are now.


  • Banned

    @sockpuppet7 so, you don't actually have an opinion on whether explicit declaration of all error kinds are good or bad?



  • @Gąska I'm not too attached to my opinions, that are rather unstable.

    Let's say that you have a printf function like this:

    void printf(...) throws OutOfMemoryException, CantWriteStdinException, InvalidFormatStringException
    

    And you have a function to write something on the console screen, like this:

    void writeSomething() throws OutOfMemoryException, CantWriteStdinException, InvalidFormatStringException {
        printf("hello, world!");
    }
    

    Now, "writeSomething" and everything that use it would are declaring all those exceptions, and OutOfMemoryException is too generic, maybe printf is some bad implementation that tries to alloc 4GB in a temporary buffer.

    So, maybe it is better to write it this way:

    void writeSomething() throws PrintfFailedException {
        try {
            printf("hello, world!");
        } catch(Exception e) {
            throw new PrintfFailedException(e);
        }
    }
    

    I think you can see where this is going


  • Trolleybus Mechanic

    @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    But is it because you always catch Exception and never its subclass out of habit, or because you think there's actually zero value in knowing which particular errors a given function might encounter?

    I didn't say I always catch Exception. I think the knowledge of what particular errors a given function might encounter provides doesn't give enough value to pay for the "throws" clause.

    Just to make it clear - if the throws clauses were less obnoxious (I don't know how; just assume for a moment they'd be), would you be in support of checked exceptions or not? Do you think the value of knowing the possible errors is significant, or within the margin of error from zero?

    I guess I don't see the value beyond documentation. About the only thing I'm going to do differently is maybe do a "connect to something" retry. I'll get the needed exception from the jdocs and/or the debugger. Beyond these few cases, I want the exception to just bubble out and fail a web request or whatever other backend work I'm doing. Fail the operation and log it.

    Plus if I use a library with its own checked exceptions with its own types, I wouldn't want to propagate those throws clauses up. And wrapping those in RuntimeException or making my own exception classes for things I don't care about is pretty dumb.

    If I'm using something like Integer.parseInt and it throws a format exception, that means there's a bug in my code. It is not something I should handle.

    When writing C#, scala, and kotlin, I've never missed checked exceptions.


  • Banned

    @sockpuppet7 well, there's a difference between throws PrintfFailedException and throws Exception. Also, there are cases where you want to know exactly what went wrong (like the example you posted earlier, with UserDoneSomethingWrongException and other kinds of exceptions). Also, if you were in the world where all exceptions are checked (except OOM and the "bytecode is corrupted or the OS ate my homework" kind), then the lack of throws clause could be taken as a guarantee that a given function never throws - which significantly reduces the amount of catching you have to perform. There's surprisingly high number of functions that can never fail in any way (except OOM and "OS ate my homework"). It's just hard to notice that in the world where anything can throw anything.


  • Considered Harmful

    @dkf I've written large amounts of Java without an IDE.

    Yes, you should always use an IDE.


  • Considered Harmful

    @dkf You appear to be catching Error. This requires a really good reason unless you are Chuck Norris. You know you can, as your catchall, catch Exception like a sane person? So that when you repent and stop catching Error, you actually won't still be doing so?



  • @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    then the lack of throws clause could be taken as a guarantee that a given function never throws - which significantly reduces the amount of catching you have to perform

    No it doesn't, all these checks are performed on a higher layers where there is zero chance of things never throwing.

    Unless you're using something shitty with checked exceptions where your base class won't let you bubble your exception up. So you end with the catch, log the trace, swallow and return null.


  • Considered Harmful

    @sockpuppet7 You rethrow wrapped in a runtime exception, y'know, and deal with the error where it makes sense. You can swallow the exception and whine about it but you also can actually still handle the condition.



  • @Gribnit said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @sockpuppet7 You rethrow wrapped in a runtime exception, y'know, and deal with the error where it makes sense. You can swallow the exception and whine about it but you also can actually still handle the condition.

    Then @gaska will yell at you for making Liskov cry. You can't win.


  • Considered Harmful

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @Gribnit said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @sockpuppet7 You rethrow wrapped in a runtime exception, y'know, and deal with the error where it makes sense. You can swallow the exception and whine about it but you also can actually still handle the condition.

    Then @gaska will yell at you for making Liskov cry. You can't win.

    Oh, don't mind him, he still believes in things.



  • @Gribnit It's like that getter and setter thing. You have to pretend you didn't notice the emperor has no clothes, or they'll think you're lazy and incompetent. Then we end being the people on this old joke:


  • Banned

    @sockpuppet7 getters and setters for the sake of having getters and setters are stupid. But LSP has very practical implications, just like high test coverage. There are lots of people who oppose both because they don't understand the real benefits of those things.



  • @Gąska Liskov is one of the few of these principles I'm OK with

    Single responsibility principle - Good, there is very little reason to break it that makes any sense
    Open/closed principle - That's useful when you get a library without source. That doesn't happen very often. If it your own code, trying to follow it create more problems than it solves.
    Liskov - ok
    Interface segregation principle - Don't recall any code that do that. I think it would cause more problems than it would solve
    Dependency inversion principle - Stupid boilerplate that makes code a lot harder to read (if you use that stupid DI frameworks). Same as the O, could be useful if it's on a library, without stupid frameworks.


  • Banned

    @sockpuppet7 agreed wholeheartedly, except that even in application (ie. non-library) code, I find DI (manual via constructors, not the framework bullshit) to be great for increasing testability.


  • Considered Harmful

    @sockpuppet7 This code will see you through the dark times.

    @SuppressWarnings("unchecked");
    public class Throw {
        public static <T extends Throwable> void t(Throwable t) throws T {
            throw (T) t;
        }
        public static <T extends Throwable, V> V v(Throwable t) throws T {
            throw (T) t;
        }
        public static <T extends Throwable> void u(Unchecked u) throws T {
            try {
                u.invoke();
            } catch (Throwable t) {
                throw (T) t;
            }
        }
        public static <T extends Throwable, V> V uv(UncheckedValue<V> uv) throws T {
            try {
                return uv.invoke();
            } catch (Throwable t) {
                throw (T) t;
            }
        }
        public interface Unchecked {
            void invoke() throws Throwable;
        }
        public interface UncheckedValue<V> {
            V invoke() throws Throwable;
        }
    }
    


  • @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    sockpuppet7 agreed wholeheartedly, except that even in application (ie. non-library) code, I find DI (manual via constructors, not the framework bullshit) to be great for increasing testability.

    Tests are another thing that can be useful, but I think some people have gone too far.



  • @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    void printf(...) throws

    Ok, let's see:

    OutOfMemoryException,

    That's not something you should generally try to recover from. Unless a specific big allocation fails, you're not going to be able to alert the user without allocating more memory. I doubt that printf even needs to allocate memory in most cases.

    CantWriteStdinException,

    Not being able to write to the standard input stream is expected. A generic printf failing to write to the output stream can be ignored. fprintf would throw an exception if it failed to write, though.

    InvalidFormatStringException

    This is an instance of something where the compiler should be able to validate the input, and in cases where it can't (such as a variable being used as the format string), this is not the kind of exception you would catch at runtime - this is a programmer error, not a user error, so it shouldn't be caught on the user's computer.



  • @ben_lubar throw new PedanticDickweedException();


  • Banned

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    sockpuppet7 agreed wholeheartedly, except that even in application (ie. non-library) code, I find DI (manual via constructors, not the framework bullshit) to be great for increasing testability.

    Tests are another thing that can be useful, but I think some people have gone too far.

    There should be some test that ensures getters and setters exist. Not necessarily a specific one just for getters and setters. Generally, having very high test coverage is great, even if it doubles initial development time (it will significantly lower it down the line).


  • Considered Harmful

    @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    There should be some test that ensures getters and setters exist

    and there nearly always is if you do things like cover the presentation layer.


  • Banned

    @Gribnit what do you mean by "cover"? Do you mean unit testing presentation layer? Because presentation layer is usually the part that you DON'T unit test.


  • Discourse touched me in a no-no place

    @Gribnit said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    You appear to be catching Error.

    Yes, but it's just a catch-and-release to act as a filter against the clause immediately below it. Without doing that, the catch of Throwable elsewhere (which I do need because of the rethrow out of the InvocationTargetException) will act to really catch Error and I don't want that. That code is about as simple as it can possibly be to do what it needs to do, and I wouldn't have needed quite so much complexity if I wasn't having to worry about Throwable itself (usually a sign of deeply funky code).

    You do know that the order of catch clauses matters?


  • Discourse touched me in a no-no place

    @Gribnit said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    I've written large amounts of Java without an IDE.
    Yes, you should always use an IDE.

    Exactly. I've also done that and reached the identical conclusion. I also know for sure that you shouldn't build Java code with make; the results of attempting to do that are always unhappy.


  • Considered Harmful

    @Gąska I've got tests that exercise the views, for instance, because fucking those up is something I should be able to catch in the build. Presentation layer is an example, though, of a place where most/all your getters will be called. Having a specific unit test for getters and setters of a mutable object is a bad idea, in general, since it masks the case when a field has become useless. You will be exercising a field in other code if you are using it, and you should probably have tests for that code.

    However, if you do any special magic bullshit, you should have tests for that specific bullshit.


  • Considered Harmful

    @dkf said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    You do know that the order of catch clauses matters?

    Yes. You are still catching Error and Throwable in your code.


  • Banned

    @Gribnit the thing is, if you're unit testing views or whatever, then in a properly designed code, you shouldn't be testing the business logic classes at the same time. Integration tests, sure - but not unit tests. In unit tests, all business logic should be abstracted away.



  • @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    There should be some test that ensures getters and setters exist. Not necessarily a specific one just for getters and setters. Generally, having very high test coverage is great, even if it doubles initial development time (it will significantly lower it down the line).

    That's controversial. I've seen people complaining they spend more time fixing bugs in the tests that bugs in the code itself, and that tests actually made refactoring slower.


  • Notification Spam Recipient

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    go pound sand*
    (* whatever that means)

    Because hitting sand effectively doesn't do (hardly) anything.


  • BINNED

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @ben_lubar throw new PedanticDickweedException();

    This, also, isn't something that should be caught on the user's computer. ;)


  • Banned

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    There should be some test that ensures getters and setters exist. Not necessarily a specific one just for getters and setters. Generally, having very high test coverage is great, even if it doubles initial development time (it will significantly lower it down the line).

    That's controversial. I've seen people complaining they spend more time fixing bugs in the tests that bugs in the code itself, and that tests actually made refactoring slower.

    Bad programmers will write bad code no matter what. All the good coding principles are designed with good programmers with mind, and they actually make good programmers write even better code. When you design things with bad programmers in mind, you end up with PHP.


  • 🚽 Regular

    @dkf said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @pie_flavor said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    And how, pray tell, do you know it's guaranteed to be an integer?

    Presumably by some higher-level constraint elsewhere (e.g., it was picked out of a bigger string by a regular expression that matched a digit sequence).

    Everyone here should know that the real world is never that ideally simple.

    Note that to check if a given thing is a number, the recommended way of checking if it is a number is to parse it with Integer.parseInt() and see if it throws an exception.



  • @Gąska IMO low level unit tests are something invented by people on dynamic languages to compensate for the lack of a decent compiler.

    If I were to work with something testable again, I would probably do tests that map directly, or almost directly, to the program use cases. I'm not going to write a test for a 20 line class.

    The code I'm working right now would need to written from scratch to be remotelly testable, anyway.


  • Discourse touched me in a no-no place

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    Now, "writeSomething" and everything that use it would are declaring all those exceptions

    In reality, there's probably more than that. I/O has an absolutely terrifying number of ways it can go wrong. :(

    However, OOM is a tricky one to catch in general as running out of memory depends on global system properties (possibly including that of other processes!) and can act as an embuggerance at all sorts of random places (anywhere that does new) so that's usually left as an implicit. Similarly, an invalid format is usually a “programmer done fscked up” exception, and those are stuff that you don't normally catch because you shouldn't be writing wrong format strings to start with. Without those two, you've really got just the exceptions due to failing to actually write the output, which are about a zillion weird and wonderful things.

    Letting end users (as opposed to app authors) specify format strings is a different class of Very Bad Idea. The equivalent in C is actually labelled as a CVE, it's that bad (because it lets you do fun and games with raw memory); managed languages are way better, but it's still dumb to expose format strings to users…


  • Considered Harmful

    @Gąska said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    @Gribnit the thing is, if you're unit testing views or whatever, then in a properly designed code, you shouldn't be testing the business logic classes at the same time. Integration tests, sure - but not unit tests. In unit tests, all business logic should be abstracted away.

    why would I mock a damn pojo that just carries state? don't be a twerp.

    Likely you miss that a view that can be tested has a contract of what it expects from the model objects it displays. Consisting of the model objects, which there is no reason to mock. How they're generated? Yes from a mock / fake whatever for a view test.

    Christ, you keep running for that soapbox somebody's gonna kick it into splinters one of these days.


  • Considered Harmful

    @Gąska
    PHP and a lot of other stuff. The thing is - perhaps one should want to design with bad programmers in mind, because that's the sad reality of almost any employment category on this rotting ball of compost - a good part will be competent just enough to not get fired *checks mirror* and some of them will be worse.

    Arguably, having no understanding of coding principles and making a mess is no worse or better than misunderstanding them and making an elaborate mess. There's nothing worse than an educated idiot *checks mirror*


  • Considered Harmful

    @sockpuppet7 The strong argument for not testing specifically for getters and setters for your models is that everything else uses them and there is (usually) no earthly reason to mock them. If you make tests to exercise your models you will cushion yourself from finding out how useless your initial model was.



  • @dkf printf is an horrible function that fails in so many ways it belongs to thedailwtf front page, that is why I picked it as an example.

    The platform I'm using has a broken sprintf, that even when you give it a null pointer to make it return how many bytes your parameters will need to write, like we do on linux and windows, it will result in a buffer overflow if you need more than X bytes. For a moment I thought it simply didn't support the NULL argument, but in that case it should break even writing a single byte (writing one byte at *NULL will cause a system reboot).


  • Discourse touched me in a no-no place

    @Erufael said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    Note that to check if a given thing is a number

    In what context? And what do you mean by “number”? Which is what I meant by “higher-level constraint”. Sometimes you can prove things pretty strongly, but only in ways that are very difficult to prove to the language compiler.

    For exampl, if you're writing a language parser then you (effectively) have a RE to grab the digits from the input string, and then feed the result into Integer.parseInt() for the actual conversion (and bounds-check of the result). The outer RE? You've also got them for all the other syntactic tokens about (for language keywords, operators, strings, etc); shoving everything through the integer parser without the pre-check would be far dumber. And yes, you use the built-in number parser if you can; not doing so is far more annoying to code. But within reason…


  • Considered Harmful

    @sockpuppet7 said in PSA: dnSpy is a terrifyingly good .NET decompiler:

    broken sprintf hidden admin mode

    FTFY


Log in to reply