That's not about being smart or not.. But about brevity and a bit of experience.
I'd certainly recommend the first one, there's simply less stuff to parse, it's easier and quicker to read.
x is a pair with key of type string and value of pair with key of type string and value of type string.
The other one is overly verbose to read, it takes more effort to read and understand.. But it could have merit, for instance if you need to extract status into a variable of declared type later..
The first implementation is bad. It’s less stuff to type, but if you’re parsing it, you’re trying to understand it, and the second implementation makes the meaning clear while the first implementation is mostly meaningless. Code is written once but read many times. Be kind to your readers, which includes yourself in the future.
My comment was from my immediate experience parsing what he wrote right there and then. The first one is easy to read and quick to understand, the second on is already like "oh god, now comes a bunch of stuff" and then "okay, he declares that, it's this.. fine" "Okay, he declared this thing and then the other thing is part of that, yeah" and finally "oh, okay he wanted a variable named X with a pair<string, pair<string,string>> geesh fuck just get to the point already!"
Like, seriously, it's like the analogy with the little kid explaining every little thing in excruciating detail, actually managing to make it sound like something is happening while nothing's happening.. Don't talk up this giant thing and tell me what you want to tell me.
> The first one is easy to read and quick to understand
No it's not. Let's look at it again:
val x: Pair<String, Pair<String, String>>
What does x represent? You don't know. Actually, there's no way for you to know what the person writing this code meant when they defined this variable. Sure, you know its structure, but how is it meant to be used in the context of the application?
This is one difference between junior and more experienced engineers. Junior engineers know what they meant when they wrote the code, but often forget to consider their teammates or future selves.
The difference is that self-documenting code is bad. Code isn't documentation for humans. Documentation is documentation for humans.
One comment right above this that gives a direct example of what x "is", is easier to understand. And then the structure of this line is also easier to understand more quickly than the second example.
Is this bad?
// A tuple defining an item with a status. (item-name (status-key status-value))
val x: Pair<String, Pair<String, String>>
But even knowing what x "is" either with documentation or with verbose "self-documenting code" it still doesn't answer your question of "how this code should be used in the context of the application"
Do we save x somewhere? Do we fetch something from somewhere else based on status-key? None of this is known. You get that from description, you get that from surrounding context. Except if we write all of our code like the second example then when we start reading the surrounding context it's going to be more painful.
We're hiding the structure with extra verbosity in the pursuit of not having to write documentation or use the best tool we have for communicating with other humans... natural language.
This just describes the structure. Structure without intent is useless.
Comments and out of code documentation both have the same problems :
- Blindspots
- Rot
That is not to say they are useless, but they are rarely if ever sufficient.
In the end, the most complete and trustworthy source of truth for the code is the code itself.
Help others by having your code describe your intent. Using appropriate variable name and using appropriate types _help_ to keep displayed intent in sync with reality.
Another point to consider is that for the same intent the structure may need to change, to evolve. Proper typing can insulate the code from those changes.
> Comments and out of code documentation both have the same problems : - Blindspots - Rot
This has nothing to do with the usefulness of comments (which is what i'm promoting). And no comments by themselves aren't sufficient for a system to do things. However, code by itself isn't sufficient to express intent. Code in isolation only serves to express a past *interpretation* of intent (not necessarily error free).
> In the end, the most complete and trustworthy source of truth for the code is the code itself.
For implementation I would agree. But not for intent.
Both versions of the code presented originally only give us the structure. The latter gives us some names. But neither gives us intent.
> Another point to consider is that for the same intent the structure may need to change, to evolve. Proper typing can insulate the code from those changes.
I think I can agree with this in some cases and not in others. Basically it boils down to if a new type is really warranted. My opinion is that new types are warranted if there is some functionality that is unique to that type. That's hard to say in this example because neither of the examples actually gave us intent or context. But if it's the case of we're only going to be doing "String" things and "Pair" things then I don't think it's warranted to invent new types to do the things the existing types already handle. It's extra abstraction and indirection.
As another commenter pointed out as well, a nitpick about the single letter variable name. It's something done in both of the original examples. It's bad in both but it's glaringly obvious in the shorter example. But changing the names here also really doesn't help us with the intent of the code. A good comment/description would.
>The difference is that self-documenting code is bad. Code isn't documentation for humans. Documentation is documentation for humans.
Surely you're joking? Code is meant to be read by humans. The comment above your single-letter variable doesn't count - just give it a proper name and use the type system.
The single letter variable is present in the second example for the thread that prompted my response. So the "single letter variable" argument is simply irrelevant. Regardless I wouldn't use a single letter variable name like this, so I do agree.
And no code is not meant to be read by humans. The generation of programming languages we have are simply the best we could come up with so far to approximate natural language. They're easier to read than previous languages, but they're not even close in expressive power to natural language.
Do we talk in code when discussing what a feature is or should do? Generally no. Do we use code to communicate in standups? Generally no. I don't see how it's joking to make the claim that humans understand natural language descriptions better and more readily than code descriptions.
As far as using the type system to document in this case. It just all depends on the context and the actual purpose of this code (which we were not given). My opinion is don't go out of your way to add more layers of abstraction just so you can have types with specific names. It's only warranted in my mind to do this when you need to add complex functionality specific to a type.
We very well might not need to do any of that. But unfortunately neither of the examples, even the self-documenting style one, explain the actual purpose of this code to us. Maybe if they had a bit more description for humans we could ponder on it further.
Also which would you rather change, a type system, or a comment?
Have you ever seen some sheet music, a schematic, or an equation and thought it would be more clearly expressed as a paragraph of English text? Those are meant to be read by humans too.
You don't have to abstract everything, but I like to use a type to add meaning. `String` and `Pair` are especially vague. Was it `UserName` or `UserId`? Was this the status code, or the message we want to display to the user? These are questions that can be answered (and mistakes that can be prevented!) by the type system. You can't get that with just a comment.
> Have you ever seen some sheet music, a schematic, or an equation and thought it would be more clearly expressed as a paragraph of English text? Those are meant to be read by humans too.
Every subject that invented each of these formats would disagree with you except for maybe sheet music (but we still use natural language heavily in music education). Do we learn how to interpret schematics, equations, and so on simply by interacting with them directly? It's possible but rare. These formats are consumed by humans, but they are definitely not native to us. To some degree written text isn't either, but it's much closer to speaking than any of these formats is.
I can understand your second point, but also it can be done in better ways than the two examples we were forced to start with. I'm not saying we do/don't have to abstract everything. But if we just want "names" or to know which arguments are what in a one line piece of code a comment is perfectly fine. Obviously as complexity grows we want more of what you are saying, but so far all we were dealing with is essentially a list of three values. And for that I just think two extra classes is too much. Maybe I'm arbitrarily being a minimalist, it's subjective.
If I had it my way java lambdas would be better and all of this would look so much nicer :)
It doesn't have to be a full-blown class, I'm happy with
type UserName string
If I can't have that, I'll probably grumble and see how close Lombok can get me to that. It's gotta be close, right?
>These formats are consumed by humans, but they are definitely not native to us. To some degree written text isn't either, but it's much closer to speaking than any of these formats is.
Humans are wonderful things! We have so many means of expression outside of speaking. Have you ever heard those code audiobooks they tried to make for traveling engineers, or a python spelling bee? I'm sure there's something linguistic going on in written text that I don't fully understand.
What happens when you pass that variable around? Imagine looking at a function that takes "Pair<String, Pair<String, String>>". What the heck is it?
Well, since we decided to use opaque types, we now have to do a bunch of spelunking. We have to grep around for usages and trace that parameter alllll the way back to where it was defined in order to find that "useful" comment. Or, we could just use better types!
Don't need them when your types are well-documented. Whereas when you pick bad types, you have to do things like remember to copy-paste that comment explaining what it is, and keep them all up to date. How is that a better solution than just once taking the time to create a descriptive type?
Sorry, I don't copy paste and I don't create a bunch of types until code becomes complex enough to warrant it. And also writing comments is a more involved and important process than just copy pasting them around.
both snippets suffer from being stringly typed, that is not what is being discussed. Change the string types to something else mentally if it bothers you that much.
Used to do a lot of Perl, which is a wonderful language for writing obfuscated code. If something is an advanced feature of the language and I felt the need to use it, I would ad a comment explaining what the code was doing.
It's a good idea to write _what_ something does, especially when the algorithm seems very "generic".
I'll a thousand times rather have
// Create users from id's
userList = theblackmagiconeliner;
Than a 20 line version of it where I'm left having to read through it to determine "oh, it made users from a list of id's" or even just.. having to read through it to find out if there's other stuff plugged in because "it could go there" or even just finding where it ends and actual work starts.
userList = userListFromUserIdList(userIds);
is also perfectly acceptable, but if it's only used once in that file, I'd prefer the blackmagiconeliner..
If it's only used once in the project, I'd also prefer that to having another file just for that function. Just do the work and get on with it.
Put the 20 line version in a properly named function, even if it's only used once. There are two reasons for this:
1. You're probably doing something else where you don't want to stop and decipher a magic one-liner. That comment is a great name for that function.
2. Now that you have a function, write a test. Now that you have a test, your coworkers won't be tempted to tuck extra stuff in that function, because everyone hates editing tests.
Exactly because now.. what used to be a menial computation required to get from A to B, has become a Thing, its existence has been justified not just by the declaration of a named function, but it has been blessed by the allocation of holy inodes and filesystem entries and versioning control entries, and oh god no, now it's been promoted to "actually important piece of infrastructure" by the greasy hand of a unit test. It has become a goal in and of itself.. What used to be a worthless piece of "I have Id's I need user objects"-getting-it-to-work has ascended and become immortal. Hours of work will now replace minutes, it adds drag to the entire system, not just in code size and bulk, but in developer mindshare, it exists and must therefore be considered and payed some amount of respect.
It is painless to delete a single line of "doing stuff" when refactoring something..
But alas, in the end, the function call is deleted, but it lives on, either unreferenced or shows its ugly face in other parts of the code, now mutilated and mutated to support "that thing that sounds like what it did but not quite"..
No, flamethrowers alight! Kill it with fire! Let it never be born.
And I'm actually very much in favor of DRY. But that mainly is about repetition, not about spawning as much "structure for the sake of structure" as possible.
That single line of magic is still a Thing, just a thing without a name or any way to check that you didn't make a mistake in writing it.
If this magic single line is a map over a list, that's fine - but I've seen some deeply unholy one-liners. Like, 600 characters for no reason. Don't do that!
Most of my coworkers are Java and .NET developers.
When we have to get down to the C, lately I've thrown away everything they try to migrate, and write something more sensible.
There really isn't any reason to add comments to this code. No one will ever read such commentary while I am employed. It does me no benefit, so I stopped.
I'd certainly recommend the first one, there's simply less stuff to parse, it's easier and quicker to read.
x is a pair with key of type string and value of pair with key of type string and value of type string.
The other one is overly verbose to read, it takes more effort to read and understand.. But it could have merit, for instance if you need to extract status into a variable of declared type later..