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?

+3


source to share


6 answers


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 */

      

+3


source


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 ( PUN FUN is intended)
+2


source


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.

+1


source


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."

0


source


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).

0


source


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.

0


source







All Articles