Pop Quiz! C#/Java/C++/Javascript/(probably others, too!) programmers, off the top of your head, what's the result of evaluating a variable assignment? In other words, to take a specific example, what is the output of this Java code snippet:
int n; System.out.println(n = 50);(Feel free to substitute in Console.Out.WriteLine (C#) or good old printf (C++) for the System.out.println in that snippet, depending on your language of choice.)
The answer is: 50. In Java and the other languages mentioned, the result of the evaluation of a variable assignment is the value being assigned.
I just came across this construct myself for the first time while I recently was doing a code review of a colleague's Java code. Somehow, prior to that code review, I had managed to go for over a decade of developing in these various languages without running across this!
The reason I hasn't run across this before may have to do with code readability. Doing two different things at once (in this case, a combined variable assignment and evaluation) often isn't very good for code readability (and therefore for ease of maintainability); in the general case, then, it probably makes the most sense for the variable assignment and evaluation to just be separated into two separate lines of code.
However, as my colleague's code demonstrated, combining an assignment with an evaluation can be useful when setting up a loop where the same statement is executed to assign a value to the loop variable both before the loop starts, and on each subsequent iteration of the loop. For example, here's a Java example of reading data from an input file a line at a time, using a java.io.BufferedReader:
String inputLine; while ((inputLine = bufferedReader.readLine()) != null) { // Do something with inputLine... }
The while statement in this case combines the assignment of the variable inputLine to the line of text read from the BufferedReader, with the check to stop looping when inputLine is null.
In the past, I've written the same logic in this manner:
String inputLine = bufferedReader.readLine(); while (inputLine != null) { // Do something with inputLine... inputLine = bufferedReader.readLine(); }
I had always been kind of annoyed over the need to repeat the assignment (inputLine = bufferedReader.readLine()) in two different places.
For writing loops like this in the future, I'll have to think more about whether the gain in code brevity (and debatably, in elegance) from using the former approach (the combined assignment/evaluation in the while statement) is worth the potential cost for future maintainers in the readability of the code.
Be careful. Soon you will start to appreciate the ternary operator as well. :-)
ReplyDeleteString greeting = (name != null) ? "Hello " + name : "Hello Anonymous";
Code readability is a subjective concept. In order for code to be readable, the assumption is that the reader will understand it. In the example that you give, that code is readable for those that understand what is going on.
I don't think that you sacrifice code readability for brevity, as long as the audience that is reading the code understands what is happening. In the case that the audience doesn't understand what is happening, then either one of two things must occur. The reviewer can take the opportunity to learn something new, or the developer has to change their code so the reviewer can understand what is happening. In situations like the one that you presented, I would hope that the reviewer would take the opportunity to learn something new.
Another reason to avoid contructs like this is to make it easier to debug. Depending on the symbolic debugger, it might not be a problem, but inserting a quick printf (substitute your favorite console output) is much harder when assignment and evaluation are combined into a single line. With that said, for the code in question a printf of the case that fails the while makes no sense, so it isn't a problem there. In more generic usage, I can think of cases where explicitly separating assignment and evaluation is definitely advantageous for debugging/reading (well, in my opinion, anyway).
ReplyDeleteThe coding for debugging purposes is a valid argument. If you structure your code in a certain fashion, then that does make it harder to debug. I would stress the term harder, as oppose to impossible for a number of reasons.
ReplyDeleteOne reason is that you can always, highlight the equation and instantly get a value that the equation evaluates to. At least you can do this in Eclipse, so I can imagine that you can do this in other IDE's as well.
It if funny that you mention the debug scenario. While attending a conference where Uncle Bob Martin was speaking, he asked if anyone there was familiar with the Debugger for their particular language. He asked if anyone was really good at the short cut keys for Step Over, Step Into and such. Some people raised their hand, and he followed it up with that should not be a skilled to be envied.
Reason why that is not a skill to be envied is because we all should be doing Test Driven Development, instead of debugging. Using Test Driven Development tactics, you can prove what the value of an equation is and how it will behave under the test circumstances.
What are your thoughts around Test Driven Development and how it relates to the Debugger?
Carlus, I'm still fairly inexperienced at Test-Driven Development, but even given a project built using good TDD practices and with a set of test cases with 100% code coverage, there still may be cases where use of the debugger is needed, at least hypothetically.
ReplyDeleteTo take a contrived simple example, consider a method that does a few math operations, including a division. Even if there are one or more unit tests that prove that the method returns the right result for several different inputs, the method could still blow up (throw an exception) if some specific input is passed to the method that results in the divisor in the division operation being 0; which in turn results in a division-by-zero exception. In that case, it might be necessary to step into the logic in a debugger in order to see what is going on.
As I mentioned, this example is somewhat contrived; hopefully the programmer in this case would have anticipated and handled the division-by-zero case, or at least the exception message and call stack would provide an obvious pointer to the problem (and thus debugging would not be necessary to find the problem). There are most likely more realistic examples of the same variety of problem out there in the real world, though!
Once again, I am not knocking the debugger....well not entirely, anyways. I am knocking the use of the debugger over the use of tests.
ReplyDeleteIn your example, you are looking for the problem scenario. You know what the bad input is that causes the problem, because you are stepping through the code anticipating it. Once you see the problem you can make the necessary adjustments to resolve the issue.
In my opinion, this approach works, but it could be enhanced through the use of tests. If you do not use a test, that means that the issue can still crop up under the same conditions (especially if someone changes the code after you have made the fix).
You would start by creating a test that shows the exception - which should be obvious since you know the input which would put you in a failed state. Then you change and modify the code until the test passes. The benefit of this is that this test will always be ran. And since most programmers are "lazy", whenever you deploy this product again, you don't have to worry about running it through the debugger in order to make sure that the problem doesn't exist or that someone has inadvertently brought the defect back into your code.
Carlus, I apologize, I think I probably wasn't clear in my previous comment. I wasn't advocating the use of the debugger in the coding phase of the development cycle as a means for establishing that a piece of code is behaving as expected prior to deployment. Rather, I was suggesting that even for a piece of code that does have unit tests implemented, the tests aren't necessarily a guarantee that nothing will go wrong in production; and in the case that something does go wrong with the production code, the debugger can be a useful diagnostic tool.
ReplyDeleteI also agree with the TDD guideline that when a bug is fixed in a piece of production code, a new unit test should be added to the existing test suite for the code that tests the failure case that led to the bug, to ensure that that particular bug can't be re-introduced in the future.
Jon,
ReplyDeleteNo apologies necessary. I always welcome a great conversation.
I completely agree with your last statement. Even if you do have 100% test coverage, that does not ensure that you will avoid production issues in the future.
Looking forward to hearing more from you in the future.
Thanks
Carlus