According to https://softwareengineering.stackexchange.com/a/180616/432039 suggested, I know the answer advised “auto” should be used instead of the actual type when declaring variables.
However, the question is about the readability of code before and after using auto, but I found the biggest problem of “auto” is not the readability, instead “auto” hinders me to search the code that I need to fix by using keyword like ‘MyClass ‘ in variable declaration like “MyClass myclass = …”.
For example, suppose I have a c++ mobile game project that needs to load user settings using DTO “UserSettings”, and UserSettings may be loaded from game server, mobile device, or edit by user at some pages, eg (ignore .h and class definition for simpler code):
OldUserLoginPage.cpp
OldUserLoginPage::loadUserData(){
//some code call http
UserSettings userSettings=loadUserDataResponse.userSettings;
//some other code
}
UserResumePage.cpp
UserResumePage::viewDidLoaded(){
//some init code
UserSettings userSettings=AppData::getUserSettings();
//some other init code
}
SettingsPage.cpp
SettingsPage::onLoadDefaultButtonPressed(){
//some code
UserSettings userSettings=DefaultUserSettings::getInstance();
//some other code
}
When the game works fine over few months, suddenly a use reports that the user settings seems failed to save, and I’m quite sure I would forget about which .cpp contain UserSettings, and also I would forget about loadUserDataResponse.userSettings, AppData::getUserSettings() and DefaultUserSettings::getInstance(), except I know I can search for keyword “UserSettings ” to list all parts of codes that handle user settings.
However, if I just use “auto” instead of UserSettings, I can’t search for keyword “UserSettings ” to find which part of codes that use UserSettings. Although I may also able to find the related code by browsing each .cpp individually after using auto, search for keyword “UserSettings ” helps me a lot faster to find and fix the bug in the code. Also when a new teammate enters the team and start maintaining the code, the teammate may be unable to know which actual page refers to ???Page.cpp immediately, but the new teammate can search for keyword “UserSettings ” to find which part of the code in the project that may contain the bug.
Also even in a single .cpp, I find the type in variable declaration may also help me to find the related code quickly, for example, suppose there is a page about buying items and magic stones:
ShopPage.cpp
ShopPage::method1(){
//some code
this->updateTotalCost();
}
ShopPage::method2(){
//some code
this->updateTotalCost();
}
.
.
.
ShopPage::updateTotalCost(){
//some other code
double totalCost=item1.quanity*item1.price+item2.quanity*item2.price+...
}
Also after some day, suppose some users find the cost seems calculate wrongly, I forgot which function and variable name handles the cost calculating, except I remember the cost must be “double” type, then I can search for “double ” to jump to the related code to investigate the problem.
While I agree “auto” may help me to write the code faster, code is being read and maintained more often than being written, so I would rather write c++ as if “auto” never exists, so that I can search for the code by variable declaration with specific type in order to find and fix the code quickly. So my question is, is the reason above the rationale to avoid using “auto”?
1
No, I don’t think this is a valid reason to avoid all usage of auto in general. It may be a valid reason for certain cases (though both of your examples look somewhat debatable to me).
Let me first say I disagree partially with Herb Sutter’s accepted answer to that other question, that you should use auto
as default, as long as you don’t want an explicit conversion. When you read the comments to that answer, you see I am not alone with my opinion, because that strategy can have a negative impact on readability.
The cases where I use auto
are mainly the cases where
-
the type name would be unneccessarily repeated on the very same line
-
the exact type name would be something like a longish technical iterator type name and does not really matter
In the first case, usage of auto
will not degrade the “global searchability”, and in the second case, it is unlikely you will ever have to make a global search for the type name.
Let me comment on your examples:
-
your second example shows a case where a global search for the type name makes no sense –
double
is as unspecific asauto
. Still, I guess also Herb Sutter would agree to use an explicit type name – in case you want to make sure totalCost has the typedouble
, regardless of what types are used on the right-hand side. That’s what Herb means by “beeing committed to a specific type”. -
in your first example, I don’t think it is a good strategy to start with a global search for
UserSettings
when searching the root cause for a certain error. When a save operation “fails”, I would first set a breakpoint into the save operation itself and check if the operation is executed (and fails), or if it is not executed at all. For the latter case, as the next step, I would find out from where I expect the save operation to be called and set breakpoints into all those calling points. A good IDE might show you all potential callers, without such an IDE, a global search for the name of the save operation would be more useful.Of course, when your save operation was simply named
save
, and you have 30 other classes which a membersave
in your codebase, as a last resort, one could make a global search forUserSettings
and hope the returned number of places is smaller than the number of places with the wordsave
in the codebase. But as you see, the global search for a class name is probably not always as useful as it looks like at a first glance.
It’s certainly a valid reason.
Whether that’s enough of a reason to avoid auto
in your situation – that depends on whether there are superior reasons for using it. Presumably you can find such reasons in the design documents that led to introducing auto
in the first place; here’s a paper by Stroustrup et al. that introduces the concept.
(Personally, I’m a huge fan of type systems doing work that you’d otherwise have to do in your head. So to me, the ideal way of doing type inference would be simply to write “auto” and the IDE replacing it with the type that it has inferenced – which would combine the advantages of both worlds. But apparently not enough people think that to make it happen.)
3
It depends on how clever your development software is. Searching for all uses of MyClass as text is easy. But some IDEs have the ability to search for MyClass as a symbol as well. That could be by using a macro with an expansion containing “MyClass”, use of “super” in a subclass, or any use of “auto” which produces a MyClass, MyClass& and so on.
Now if you want a MyClass instance and nothing else, feel free to use “MyClass” and not “auto”. You do use auto if you don’t really care about the actual type. For example, if a function returns an iterator, that is often a complicated type, and you don’t really care about the type, so you use auto.
It is true that the code is being read and maintained more often than it is written. But that’s at lest one more reason to use auto
!
auto as maintenance accelerator – change propagation
For the maintenance of code, auto has indeed often (not always) the advantage of facilitating abstraction and ease decoupling from the types.
Take your example:
double totalCost=item1.quantity*item1.price+item2.quantity*item2.price+....
Imagine that you find out that finally double
is not sufficiently precise for the quantity, and you’ need to go for long double
to avoid unacceptable loss. You’d have to go carefully through your code, and for every double
look if it’s concerned by the change or not. With auto
, you let the compiler automatically propagate that change and will have lest risks of forgetting one occurence, which might cost you some runtime approximation that you would not necessarily even notice.
auto as maintenance accelerator – slice protection
But there’s more: auto
also avoids nasty slicing errors, when you want to use MySpecialisedClass
subclass instead of MyClass
and forget to adapt one declarator:
MyClass xyz("some", arguments);
... // lots of code
MyClass uvw = xyz; // looks fine
But after refactoring:
MySpecialisedClass xyz("some", more, arguments);
... // lots of code
MyClass uvw = xyz; // still looks fine - but slicing occurs !
But what with reading ?
First of all, there are a lot of languages out there with no types specified:
- Take for example Python, which is strongly but dynamically typed. Python coder usually not complain that their code is difficult to read because the type is not shown explicitly.
- Take also JavaScript. Do JavaScript complain that their code is not readable ? No, the main concern is when accidentally a variable causes a runtime error because of the wrong type. So the complain is more about type safety than readability.
- Take some modern languages, like Swift. Swift also encourages type inference and
auto
-like constructs withlet
andvar
.
Why would it be more difficult to read if it’s C++ then ? Because of habit. And habits still prevent people to embrace modern C++ and its benefits.
7