|
Richard Deeming wrote: There's one feature of C# 8 you'll like, then!
Quite so. Still, I wonder where they're going with this -- nullable value types are great, and so it makes sense that the complement, non-nullable reference types, should exist, it certainly provides symmetry.
But oh boy, there are going to be thousands of warnings in the code base I work with at work when (if ever) they opt-in for this. But it'll probably be a few years before they get their build systems onto the C# 8, which by then they still will probably be several versions behind, as they are now.
|
|
|
|
|
Interesting comment. I was returning the null to abuse the nullco and short circuit the chain, even though the (actual) list itself isn't null, just empty. But you make a good point about things that shouldn't be null. Returning a null when empty does (though only shortly until the nullco) create a null list. Not using the nullco immediately after would turn a null list loose.
|
|
|
|
|
|
Paragraphs, then collapse all the functions I'm not actively messing around with.
|
|
|
|
|
If you use VS, you can change the formatting parameters depending on who you show the code to: Before showing your code to a code homeopath, you check every single option in the New Lines and Spacing sections and uncheck the Wrapping options. Then go to the closing brace of the file, delete and retype it, and the code is ready for presentation. When you go back to the coding, you set the switches back to your preferences, delete and retype the closing brace.
If you are fond of end-of-line comments, they won't be nicely lined up at column 70 when you show your code in diluted mode. That is usually a minor problem with the homeopaths; the never look beyond column 30 of the source file anyway. Besides, the majority of them are totally unfamiliar with the very concept of end-of-line comments; they put all comments into the huge commment block over every function.
This works fine with C#; I never tried with other languages.
In the old days before we had automatic formatting, and code discussions were based on hardcopy printouts, a nice trick was to print all source code double spaced. I have had homeopaths commenting point to a few specific blank lines telling that I could drop the blank to emphasize that they really go together - never noticing that the entire file had "blank lines" between every printed line - or three or five blank lines.
|
|
|
|
|
In your example:
list.WhenNotEmpty()?.ForEach(s => s.Prepare(out var r)?.Manipulate(r));
The WhenNotEmpty()?. isn't really needed, is it?
|
|
|
|
|
No, not really in this case. Something like .When(predicate) that might have been better. Or following it with something where it did matter.
|
|
|
|
|
I was saying that you can use an empty collection with foreach, which will basically do nothing. You shouldn't need anything there.
|
|
|
|
|
Right. That's what I meant by "following it with something that did matter".
|
|
|
|
|
Ah good, learnt something new first thing in the morning, now the rest for the day.
the this class extensions parameter looks interesting. Might see if can use a bit of it.
thanks
|
|
|
|
|
|
|
The one problem I have with the everything-on-one-line approach style of programming (which I also do a lot) is that it's impossible to debug.
You don't get intermediate values and you don't get to set breakpoints at specific functions.
The readability aspect is not that big of a deal, I can read it just fine (usually).
However, since you write code only once and read and debug it a gazillion times I prefer to write my code so I can step through it.
Sometimes I put it back together for readability afterwards.
For example:
var filtered = myList.Where(...);
var ordered = filtered.OrderBy(...);
var result = ordered.ToList();
return result;
return myList
.Where(...)
.OrderBy(...)
.ToList();
|
|
|
|
|
All that depends on the debugger. E.g. in Visual Studio, if you write your code as
string Foo(int x) {
if (x == 1) return "one";
else return "not one";
} and you only want to break when "one" is returned, mark the statement return "one"; with the mouse and hit F9. It will not break for the "if" tests, but only if the true branch is taken.
|
|
|
|
|
Yeah, but I believe that's a new feature since VS2017
Just like you can now debug in-line lambda's.
It wasn't always like that (and still sometimes isn't).
|
|
|
|
|
This I can parse and validate, at a glance, without trying:
string Foo(int x)
{
if(x == 1)
{
return "one";
}
else
{
return "not one";
}
}
}
This requires me to actually read the words on screen and consciously think about them:
string Foo(this int x) => (x == 1) ? "one" : "not one";
It's not "more clear" on a macro level. It's "more clear" to you personally.
If you were one of my juniors, I'd reprimand you for using an esoteric personal style at work.
If you were a senior colleague, I'd just skip all formalities and just stab you with a kitchen knife.
|
|
|
|
|
Do you really have parsing problems with
string Foo(int x) {
if (x == 1) return "one";
else return "not one";
} This style gives me a third as many source "code lines" to relate to. I can easily overview the entire function. In a larger function, a less whitespaced layout makes it much easier to spot the start/end of loops and condition blocks etc.
|
|
|
|
|
I can parse it line by line at a normal reading speed. Takes about 3-4 seconds the first time I see it.
The initial style I can parse and verify without having to read a single word. Takes me about 250 milliseconds.
The benefit of having spacious functions is that mistakes become embarrassingly easy to spot.
Likewise, properly written functions become very easy to parse, because they look so damn familiar.
I find "less lines of code" a dubious benefit.
A cramped style is more time-consuming to debug, both in terms of setting breakpoints and retracing logged exceptions.
Also, I definitely agree functions should not grow too big.. but stacking multiple logical steps on a single line only gives you the illusion of small functions.
I say: be spacious, let functions grow, and flag them for refactoring if they don't fit on a single page.
The point is to write code that's self-evident +6 months in the future.
Quick to read, amend and fix if need be.
|
|
|
|
|
KBZX5000 wrote: because they look so damn familiar. That is the main point here.
"Readability" is not as rationally and objectively defined as we would like to think; it is highly personal. What you learned at school. Universities (at least here in Norway) tend to stress whitespace as the most essential element of good programming, so yuong programmers thend to frown at lines exceeding 30 characters.
When the code is spread over three times as many lines, I spend a lot of attention searching for the match: Hey, there is an "else" here - where was the matching "if"? Not on the line above, not two or three lines above, but four lines above.
if/else is usually fairly simple to handle, though. Braces are far worse. When you unroll seven levels of nested blocks, exactly where is the start of the block closed by the fourth one in the unstacking? You don't know which keyword to look for; everything starts with a brace. If the match is 16 lines higher up, it is much easier to match than if it is 67 lines higher up!
Also, I avoid superflous braces: There is no reason to make a block for every single-statement if, while or for clause. If you try to find the matching opening brace (like above), it is far easier if there are five candidates, as compared to twenty three.
(Exceptions are exceptions: The language "designers" did not learn from Pascal that a statement IS a block; they defined a block concept excluding a statement from being a block. Then came the excptional people declaring the exception mechanism to require blocks for all its parts. I consider statements not being blocks as one of the very big mistakes in the formal definition of the c language.)
It is not just for the braces: I really dislike "the rightward migration of source code", as it was once described. To some people, ideal source code has a left margin like a funnel (and sometimes, the underside of the funnel makes you think that you are programming lisp). Matching opening and closing braces is close to impossible except for the two or three innermost levels.
The fewer indentation levels, the better! Following a "return" statement by an "else" is meaningless. I kept it the code example above, because I know lot of coders would bark if I removed it, even though it really has no meaning, and is in a sense misleading: It seems to suggest that the code following the if(){} else{} is unconditional, regardless of the if-outcome. You actually have to open up that if-clause to discover that not only the statement in the else-clause itself but everything that follow the "else" it is conditional on that if-test.
Very often, funnel code is some variation of if - else if - else if ... but with such complex logic that a simple elseif-sequence won't do. The main point is: if (so-and-so), then do it and you are done! All the rest of the funnel is for other cases! Several times when I am the only one handling some code, in C I set up a "#define once i=1;i==1;i++" so that I can program like:
for (once) {
if (complex condition) {
...do this and that...
break;
if (another complex condition) {
...do something else...
break;
... as many processing alternatives as relevant ...
} The for loop is a dummy: Because C doesn't provide any mechamism for breaking out of an arbitrary block, we must create a dummy loop to allow the break.
Good old Z.200 CHILL has a very nice feature: Any block (including single statements) could be assigned a label; a function name is a label. The EXIT statement breaks out of the block being so labeled. Also, at the end of the block, you can optionally add the label name for easier matching. The compiler processes it, and if you have placed it wrong, the compiler barfs at you. Yet another thing: Blocks are not enclosed in anonymous braces, but with identifying keywords that tells what kind of block ends here. CHILL doesn't suffer from the lisp syndrome that a lot of C code is plauged with.
CHILL also has an EVER keyword for loop control: You can write: DO FOR EVER ... and the semantics are very clear. Embedded code often has everlasting loops. From old CHILL experience, I added a "#define ever ;;" so that I could write "for (ever)". One of the youngsters (to quote Bill Bryson: "I don't want to say that he was awfully young, but he was wearing a Cub Scout uniform") got really mad when he discovered this, went through the entire code repository for the project and edited it back to the proper way of making an infinte loop: "while (1)", with a very nasty remark in the SVN repo about "some people" who are not taking things seriously but add funny jokes in the program code...
Which brings us back to the starting point: Recognition. This youngster immediately recognized "while (1)" as a loop that would run for ever; he did not recognize "for (ever)" as such.
For those of us who grew up with Pascal and CHILL and the like, some of those C coding style rules seem to serve more as "Don't you, common man, believe that this is something you will read as anything resembling a prosa description of what the computer will do! We are trained to recognize it, but we have taken measures so that you will not recognize it!" That is one of our tools to stay in power.
|
|
|
|
|
Your first example has one great advantage: When your productivity is measured in number of code lines produced, it wins by a large margin.
The disadvantage is that to get an overview over even a fairly trivial function that really should fit in a screenful, you have to flip back and forth though a pile of pages. In any sufficiently fancy editor you can split the window so you can correlate various parts of the function logic, but then you can fit half as much logic in each tile, and that may be too little!
A second disadvantage: With all that whitespace (frequently, programmers add at least one blank line before and after every loop, after function initialization, ... everywhere!), you have to look really close to see where this function ends and the next function starts. Except that those blank line lovers usually also add a huge comment block before every function to explain that ThePriceOfApplesPerKilogram argument is the price of apples per kilogram, that pi has the value of 3.1415926535897932384626433 and similar essential information.
To some of these programmers, "readability" is a synonym for "whitespace percentage". Sort of homeopathic programming: The more diluted, the more powerful.
On the other hand: The APL ideal of "There is no programming task so complex that it cannot be solved in a single line of APL" goes too far for my taste. An example from Wikipedia -The game of Life:
life←{↑1 ⍵∨.∧3 4=+/,¯1 0 1∘.⊖¯1 0 1∘.⌽⊂⍵}
I put most regex in the same category. Geek & Poke: Yesterday's regex[^]
I'd like to place myself in the middle of the road, but I cannot deny that those homeopatic programmers raises such protests in me that I lean somewhat over to the other side. If you cannot fit every function in its entirety on a single screenful, then you have used too much whitespace.
|
|
|
|
|
This is the first time I have seen "this" being used to prefix a parameter, and Googling doesn't find it. Could you explain, please?
|
|
|
|
|
|
|
Paragraph level.
Are you thinking that the compiler does something more optimized because you wrote it as a sentence instead of paragraph? Or were you thinking that less typing benefits the project overall? Or perhaps some other underlying reason?
Paragraph style is not only more readable and understandable to another programmer reading it for the first time, it is more readable and understandable to you 6 months, a year, or 2 years later when you revisit the code for the first time since you finished it.
Paragraph style is also easier to debug and see runtime values.
When you are not the only programmer involved now or conceivably in the future, paragraph style would have a positive effect on the maintenance portion of lifecycle cost.
Another benefit of paragraph style is that it lends itself better to adding comments that explain the what and why of what you are doing.
modified 1-Jun-18 10:53am.
|
|
|
|
|
When I started I was actually thinking more about separating concepts and mechanics, or maybe better, business vs technology. But I think I lost that some when playing with creating my examples. )
In business applications (vs scientific/engineering) most methods implement some part of a business function.
At an absurdum extreme you could write the application in one procedural block with thousands of lines of branching/conditional code, where each line does no more than a single thing. It contains an atomic concept. So, you wouldn't write if(x == 1 || x == 3) , that is two concepts. At this extreme, every atomic thought is crystal clear, but the big-picture of what the method does becomes a forest-trees thing.
We decompose software into logical, understandably named chunks, like methods and classes. At another absurd extreme, we could make each concept a method - bool IsValueEqualToOne(int x) {if x ==1 return true else return false} . This is just as bad. We have only translated from a technical representation (x == 1)? to a business representation IsValueEqualToOne .
In real-world programming we balance the size and form of our chunks. In some cases, it is better to combine at a "technical" level - `if(x == 1 || x == 3)`. In others, a business level is better
if(AccountHasFunds(sourceAccount, amount)){
debit(sourceAccount, amount);
credit(targetAccount amount);
}
At the opposite extreme or "can't see the forest for the trees" is "needle in a haystack", one liners. Like the examples I gave. ( and tried to disclaim as how I would actually do it. )
So, to your question Quote: compiler does something more optimized... Or perhaps some other underlying reason?
In rereading it sounds like I may be advocating "one-liners", but not really. All the LINQ and stuff has a greater chance of compiler confusion than optimization. Maybe the best I can think of is that each method we write contains a business purpose and a technical implementation. The clearest code communicates both, and the best code does this in a maintainable way.
With LINQ style and functional becoming popular I was curious to see if people were writing more that way.
My preferred method of coding combines approaches, made easier with local functions. (I just make up "transaction syntax for simplicity)
void TransferFunds(source, target, amount){
if(AccountHasFunds(sourceAccount, amount)){
transaction {
debit(sourceAccount, amount);
credit(targetAccount amount);
}
success: {}
rollback: {}
} else { CancelTransfer(); }
[Description("Validates account balance equals or exceeds amount")]
bool AccountHasFunds(string account, decimal amount){
var accountBalance = account.GetBalance();
if ( accountBalance >= amount){
return true;
else {
return false;
}
}
[Description("Debits amount from account")]
void Credit(…){
…
}
...
}
|
|
|
|
|