Is it good practice to explicitly declare variables without "much" use in favor of readability?
So this is another "good" programming question. I've searched a bit, but something like this is often difficult to define with just a few words.
To the question: From a professional point of view, is it better to program so that the code is simplified and short (not necessarily more efficient), or to explicitly define instance variables just to assign them and return them immediately? For example:
FILE * foo(){
FILE * retVal;
foo2();
retVal = foobar();
return retVal;
}
From the above, you can immediately see what a foobar
returns FILE *
. Thus, we can extract important information more quickly from this programming style. This is true when compared to something like this:
FILE * foo(){
foo2();
return foobar();
}
Which, of course, does the same thing. However, one has to look deeper to find the same information. I prefer the latter style of programming simply because it looks better. Because of how this program will work, I doubt there are any immediate benefits from using one of these, since memory is still needed for any choice - the difference is whether the user or the compiler allocates it.
As another example of code reduction and brevity:
int foo(){
int i = 0;
while(foobar())
i++:
return i;
}
TL: DR Question -> Is it better to show clearly what is being done, or is it okay, in favor of brevity and brevity, to shorten code that does the same task but not necessarily improve performance?
source to share
Choosing between precise and shortened code is subjective due to the reason you are projecting. When it comes to maintenance, most of us would prefer a shortcode. Even students would prefer concise code, even though it conflicts with what they should prefer.
C is designed to be human readable and compile with less effort. It's procedural and very ornate-less. Another reason for coding is for readability and against time consumption.
Both of the ways you provided in the example generate exactly the same ASM code (note -O
).
.Ltext0:
.globl foobar
foobar:
.LFB13:
.cfi_startproc
0000 B8000000 movl $0, %eax
00
0005 C3 ret
.cfi_endproc
.LFE13:
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
0000 666F6F32 .string "foo2 called"
2063616C
6C656400
.text
.globl foo2
foo2:
.LFB14:
.cfi_startproc
0006 4883EC08 subq $8, %rsp
.cfi_def_cfa_offset 16
000a BF000000 movl $.LC0, %edi
00
000f E8000000 call puts
00
.LVL0:
0014 B8000000 movl $0, %eax
00
0019 4883C408 addq $8, %rsp
.cfi_def_cfa_offset 8
001d C3 ret
.cfi_endproc
.LFE14:
.globl foo
foo:
.LFB15:
.cfi_startproc
001e 4883EC08 subq $8, %rsp
.cfi_def_cfa_offset 16
0022 B8000000 movl $0, %eax
00
0027 E8000000 call foo2
00
.LVL1:
002c B8000000 movl $0, %eax
00
0031 4883C408 addq $8, %rsp
.cfi_def_cfa_offset 8
0035 C3 ret
.cfi_endproc
.LFE15:
.globl main
main:
.LFB16:
.cfi_startproc
0036 4883EC08 subq $8, %rsp
.cfi_def_cfa_offset 16
.LBB8:
.LBB9:
003a B8000000 movl $0, %eax
00
003f E8000000 call foo2
00
.LVL2:
.LBE9:
.LBE8:
0044 B8000000 movl $0, %eax
00
0049 4883C408 addq $8, %rsp
.cfi_def_cfa_offset 8
004d C3 ret
.cfi_endproc
.LFE16:
.Letext0:
.. in your minimalist, insignificant shortcut's answer and .
With that in mind, I can freely say that it is best if you just apply both correctly. And that's ... as short and clear as possible, and
/* COMMENTED */
source to share
Disclaimer: None of the following are standard.
Usually, when properly optimized, compilers will optimize most of the redundant or dead part and make the binary as efficient as possible.
With this in mind, we advise you to write code that is easy for humans to understand. Leave some of the optimization (mostly) to the compiler.
Writing code that is more understandable to people makes it
- More acceptable to others.
- Easier to maintain
- Easier to debug
- Last but not least, a Lifeguard for you (
PUNFUN is intended)
source to share
There is readability and there is debugging.
I would write your example (which by the way does not compile) as
FILE* foo ()
{
foo2();
FILE* retVal = foobar();
return retVal;
}
That way, if I need to debug, I can set a breakpoint in the return statement and see what retVal is. It is also generally a good idea to avoid overly complex expressions and use intermediate variables. First, for easier debugging, and second, for easier reading.
source to share
I am against this style, although I understand that many people do it for debugging.
In typical debuggers, you might notice the error that it might be difficult to see the values ββthat are returned immediately, perhaps.
If you feel like doing this, I highly recommend two things:
- Use C99 so you can be late.
- Use
const
.
So I would write an example foo()
like this if I had to:
FILE * foo(void)
{
foo2();
FILE * const retVal = foobar();
return retVal;
}
Note that const
it cannot go to the left of the asterisk ( const FILE *retVal = ...
), as the type will do const FILE *
; what we want is a constant pointer, not a pointer to what is constant.
The goal const
is to tell human readers, "I named this value here, but that doesn't mean I'm going to cheat."
source to share
I agree that the code is readable. However, I disagree that the first one is easier to read or even maintain.
- readbility: More code to read and understand. While it might not be that hard for an example, it might be for more complex types.
- maintainablity: If you change the return type, you also have to change the retval declaration.
Many coding styles require variables defined at the beginning of a block. Some of them even only allow it at the function level. This way you have a variable declared next to the function declaration, away from returning.
Even if it's allowed: what did you get? It can also hide coercions on return, since the compiler will also complain about the wrong return type - if you resolve most of the warnings . This will add to the quality of the code (seriously).
source to share
Short version
Only introduce a new variable when adding implicit information. Usually the case is when a variable is used to replace a somewhat complex expression
Long version
As with everything, it depends. I think the best way to approach this is to conduct a cost-benefit analysis of the situation.
The cost of using an intermediate variable (purely from a code / understanding point of view, I'm sure any decent modern compiler will optimize them), I'd say the effort of parsing the variable, understanding the definition in context, and more importantly, linking the latter using this variable with the working model of a program that someone else is reading your code means.
The advantage is that the new element, by introducing additional information, can help the reader to either form a simpler mental model of the code base or a more accurate model. And for a new variable declaration, most of the information is contained in the type or name.
Consider, for example, the following example:
if(isSocial)
return map[*std::min(d.begin(),d.end())].first;
else
return map[*std::max(d.begin(),d.end())].first;
return idealAge;
if(isSocial)
int closestPersonAge = map[*std::min(d.begin(),d.end())].first;
idealAge = closestPersonAge
else
int futhestPersonAge = map[*std::max(d.begin(),d.end())].first;
idealAge = futhestPersonAge
return idealAge;
In the first example, your reader will need to understand what std :: min does, what "d" and "map" are, what their type is, what type of map element, etc. In the second example, by providing a meaningful variable name, you essentially save the reader from understanding the computation, which allows him to have a simpler mental model of the code, while still retaining the same amount of important information.
Now compare this with:
int personAge = person.age();
return personAge;
In this case, I think that personAge does not add any additional information (the name of the variable and the method conveys the same information), and thus does not help the reader in any way.
source to share