 |
|
 |
This is a classic example of programmers being too clever by half. I was taught programming on punch cards. I was also taught the three "R"s. Readability, Reliability, and Maintainability (excuse the last spelling). This article violates the 1st and 3rd. If you are passing some parameters to a function then the first parameters should be what is going in ie by value, and the last parameters what is coming out - by reference. Then anyone can come back years later and see what the hell is going on. My field is engineering, depending on the project I have to pick up code that may not have been used for ten years and modify it for the latest codes. You soon learn good programming techniques and that it to keep it simple. Go back to the basics, think of the poor devil that has to pick up your code, understand it and modify it. Don't be too clever and don't obfuscate.
|
|
|
|
 |
|
 |
The 'truck' way is convoluted and takes a lot more source code to use. Also, it will give you hell when you try to use it when calling an external DLL. If the DLL and your application don't use the same header file (i.e., struct), the call will go through with an incorrect struct (crash/mem corruption/...).
|
|
|
|
 |
|
 |
S.H.Bouwhuis wrote: If the DLL and your application don't use the same header file (i.e., struct), the call will go through with an incorrect struct (crash/mem corruption/...).
True. This happens mostly while working on big projects containing huge number of files.
S.H.Bouwhuis wrote: If the DLL and your application don't use the same header file (i.e., struct),
then:
[1]clients of DLL receive run-time error in "pass by Train" irrespective of method(static or dynamic) used for utilizing the DLL.
[2]clients of DLL receive no error(run time or compile time) in "pass by Truck" irrespective of method(static or dynamic) used for utilizing the DLL.
Obviously if some good practice is followed by checking the external data that is received as parameters in function(which might be inside the DLL)then, code inside the DLL can return proper error.
Please refer for exception related discussion :
http://www.codeproject.com/Messages/3547668/Re-Parameter-Passing.aspx[^]
Now, when some external code(functions in DLLs) is used in application, then its better to check\ handle the errorno\ status\ exceptions return by that external code(function).
But I guess in this case of DLLs "pass by Train" is better as one can identify the mismatch earlier.
|
|
|
|
 |
|
 |
If parameters are form a logical entity in the domain you are modelling in the software, passing these parameter in a "truck" is a good idea. However, making up an ad-hoc "truck" for the sake of trying to keep the signature the same when changing the software is a bad idea, as explained by the other commenters.
|
|
|
|
 |
|
 |
Emile van Gerwen wrote: If parameters are form a logical entity in the domain you are modelling in the software, passing these parameter in a "truck" is a good idea.
Yes its correct.
Emile van Gerwen wrote: However, making up an ad-hoc "truck" for the sake of trying to keep the signature the same when changing the software is a bad idea, as explained by the other commenters.
Again its correct.
If Single Responsibility Principle is followed then the cases in which ad-hoc "truck" needs to be created is minimum.
|
|
|
|
 |
|
 |
My vote is 1.
Why?
Because if you have struct
MyStruct{
int a;
int b;
};
and function MyFn(struct MyStruct);
and later will change
MyStruct{
int a;
};
old and new MyStruct will belong to different types and MyFn will have different signatures (check full decorated names); it is totally unacceptable to use changed MyStruct in the call to unchanged MyFn (think what happens when MyFn will try to process MyStruct.b that is not present in the new MyStruct). Compiler wil not allow you to do that.
The first and second versions of MyStruct are different types, so the first and the second versions of MyFn(struct MyStruct) do have differen signatures.So your assertion about signatures not changing is wrong.
Regardung the convenience of using struct versus bunch of params: it depends.
Sometimes bunch of params makes code more readable; sometimes structs (e.g. Student or Employee) are better.
So passing params in struct saves you nothing. You still have to check all functions and function calls to ensure safety of your code, but to naked eye function was not changed, so good luck to maintenance guys.
IMHO, your article is very dangerous.
geoyar
modified on Monday, July 26, 2010 7:28 PM
|
|
|
|
 |
|
 |
Yes, this cannot be adopted in case you are providing SDK to third party.
Swapnil Khare
|
|
|
|
 |
|
 |
First of all thanks for scrutinizing the things.
I guess all the thing that are used in code should go side by side.
Its a good practice to provide all the code at the point of use.
For example:
case [1]:
struct PARAMETERTRUCK
{
int a;
int b;
};
int func(PARAMETERTRUCK stParam);
#include "abc.h"
int func(PARAMETERTRUCK stParam)
{
if(stParam.a > 256)....
if( (stParam.b > 50) && (stParam.b < 100) )
{
}
else
return }
#include "abc.h"
int driving()
{
PARAMETERTRUCK stParam;
int errno = func(stParam);
}
This demonstrates that "whatever" code, "where ever", used is available in a consistent manner.
case [2]:
struct PARAMETERTRUCK
{
int a;
int b;
};
int func(PARAMETERTRUCK stParam);
#include "abc_1.h" #include "abc.h"
int driving ()
{
PARAMETERTRUCK stParam;
func(stParam);
}
[1] This demonstrates the inconvenience caused because of keeping the data in a scattered manner.
[2] This is also error prone if PARAMETERTRUCK changes, as stated :
geoyar wrote:
Because if you have struct
MyStruct{
int a;
int b;
};
and function MyFn(struct MyStruct);
and later will change
MyStruct{
int a;
};
old and new MyStruct will belong to different types
Summary:
[1]If "whatever" code "where ever" used is provided by using "providing at the point of use concept" and some good practice is followed then, I guess there will not be any inconsistencies, even if a complex thing like "SDK" is maintained.
Member 3366567 wrote:
Yes, this cannot be adopted in case you are providing SDK to third party.
In this way the clients of code will also and always use the code in consistent manner.
[2] For benefits of pass by Truck
Please refer:
http://www.codeproject.com/Messages/3541875/Re-My-vote-of-2-Perhaps-the-article-doesnt-go-far-.aspx[^]
http://www.codeproject.com/Messages/3539388/Re-My-Vote-of-5-plus.aspx[^]
|
|
|
|
 |
|
 |
Thank for reply.
We are discussing two issues:
1) Does the change of a struct passed to a function as a parameter change the function's signature?
2) Coding practices relating to using structs as a function parameters.
1. The function signature consists of
1.1 Function Name
1.2 Parameter(s) Name(s)
1.3 Parameter(s) Type(s)
When you add/delete/change the struct members you are changing the struct type.
So, even if names of the function and parameter are the same, types of parameters
are different. The signature of the function is changed. I think you have to correct
your text.
2. Coding practices.
When you change the struct you are passing to the function you must:
2.1 Revise the code in the function's body
2.2 Look at and examine all instances the function is called
2.3 Look at and examine all other places in your code using the struct
Your compiler will not always register errors or give you warnings. E.g. if you
change member type from unsigned int to int there would be default casting and
possible errors. And I don't buy this preventive techiques you are suddesting.
Yes, one parameter is less verbose than five or ten, but in the body of the
function and at calling points you will have to use longer name (struct.member.)
If you pass params by "train" and later remove/add one, compiler will complain
"The function does not take five parameters" and like.
Again, by "train" or by "truck", the same due diligence is requested.
geoyar
|
|
|
|
 |
|
 |
This discussion helped me in fetching one more advantage of using struct (i.e Truck) not only as a parameter to function but in general while working with related data.
geoyar wrote: When you add/delete/change the struct members you are changing the struct type.
So, even if names of the function and parameter are the same, types of parameters
are different.
struct PARAMETERTRUCK
{
int a;
};
struct PARAMETERTRUCK
{
int a;
int b;
};
Conceptually, "YES", type of struct has changed.
Programmatically, "NO", no change in type as depicted by error.
geoyar wrote: 2. Coding practices.
When you change the struct you are passing to the function you must:
2.1 Revise the code in the function's body
2.2 Look at and examine all instances the function is called
2.3 Look at and examine all other places in your code using the struct
There are advantages because of [2.1], [2.2] and [2.3].
When working 'Conceptually' in situations were there are many functionalities which depend upon related data then, "Truck" can help you in managing all such functionalities.
Please refer:
http://www.codeproject.com/Messages/3541875/Re-My-vote-of-2-Perhaps-the-article-doesnt-go-far-.aspx[^]
for an example where STUDENTDATA is used in many functionalities like:
[1]vector<STUDENTDATA> m_Students; [2]Register(STUDENTDATA); [3]Unregister(STUDENTDATA); [4]PrintStudentInfo(STUDENTDATA); [5]EmailStudentInfo(STUDENTDATA); ....
Here, if the Truck i.e STUDENTDATA is changed then you get a hint that you need to perform [2.1], [2.2] and [2.3]. This can be done easily by searching STUDENTDATA keyword. Imagine if data is kept scattered ("pass by Train")then, you might need to consult documentation of projects.
geoyar wrote: Your compiler will not always register errors or give you warnings. E.g. if you
change member type from unsigned int to int there would be default casting and
possible errors. And I don't buy this preventive techiques you are suddesting.
This is purely a matter of following a particular programming practice. If the external data is checked before using in function then proper exceptions can be fired and this is the case for which the concept of exceptions exists.
geoyar wrote: but in the body of the function and at calling points you will have to use longer name (struct.member.)
longer names but in a related sense.
Here related sense can be:
[1]all the data is external, received as a parameter in the function.
[2]all the data gives info of a common purpose\ knowledge like STUDENTDATA.
etc..
Again this purely is how one looks at things.
|
|
|
|
 |
|
 |
So you agree that the function signature is changed. Then, correct your text.
Thr function signature is not an empty concept. Sooner or later you will get an ugly bug if you ignore changes in the signature. And if the compiler does not give you an error it does not mean all is OK. Sometimes it corrects your bug automatically (e.g. reserving memory for stack), sometimes compiler's team did not get some type of error might happen. What is wrong conceptually is wrong programmatically. No wrong concept is right programmatically, period.
Checking data does not help. E.g. let the salary member of the struct has type unsigned int. You check itsalue before the call. It is OK. Then, in the functipn, adding salaries $30,000 + $20,000 would give you $50,000. Change the type to int: $30,000 and $20,000 will pass the test before a call with flying colors, but the result would be negative. And the appropriate warning might be suppressed.
We are talking here about programming, and maintenance, and team work. It might be nightmare.
Regarding the passing parameters to functions, passing user types like structures and classes is known and understood very well since inception C and C++. Nothing new is in it. Would there were some meaningful gains, we would stop passing POD types to the functions long, long ago. The truth is, it is very rare that someone artificially combined not related data types into struct only to pass it to some function.
I see messages sent to you. Reaction is not so enthuastic. It seems you have to do something with your article.
geoyar
|
|
|
|
 |
|
 |
geoyar wrote: What is wrong conceptually is wrong programmatically. No wrong concept is right programmatically,
Good one.
|
|
|
|
 |
|
 |
After discussion with the author below it seemed like a good idea to crank the vote up a bit.
|
|
|
|
 |
|
 |
The content is not right...
|
|
|
|
 |
|
 |
Can you please specify in more detail
|
|
|
|
 |
|
 |
Changing the definition of a structure is changing the interface/prototype. For example, if you change the name of a field, the code that using the field has to be rewritten, even though the function "prototype" looks like the same.
|
|
|
|
 |
|
|
 |
|
 |
As Richard and Marcus have pointed out if the whole idea of currying parameters (that's piling them into a structure and then chucking them at a function) is to decouple the function call from the parameters it takes you're actually making things more dangerous for yourself when you need to change parameters. It can hide things the compiler should tell you are wrong.
Even if you go a bit further and (in C++) add a constructor to the structure to catch errors you're still ending up with a shed load of parameters being passed to one function, just not the function you originally wrote. Instead of writing:
int result = foo( a, b, c, d );
you end up writing things like:
foo_params fp( a, b, c, d );
int result = foo( fp );
Now you might say this isn't that bad - I've got an abstract entity foo_params and I want to operate on it with a function - processing it, transforming it, whatever.
When you get here why not go one stage further?
foo_params fp( a, b, c, d );
int result = fp.foo();
Hey, that's OO programming!
So the thrust of my argument here would be that as soon as you start currying parameters you're one step away from OO programming and that final step can improve the readability of your code no end and avoid the problems you're trying to avoid in the article.
Cheers,
Ash
|
|
|
|
 |
|
 |
What ever you discussed in your post was perfect guiding step towards OO Programming. In fact, the following code :
foo_params fp( a, b, c, d );
int result = fp.foo();
correctly suggests that all the necessary things (data and operations on that data) should be under one name.
But consider this case:
class Lab
{
public:
int Register(string Name, int Age);
int Unregister(string Name, int Age);
};
In this case I cannot replace Register() and Unregister() functionality with
struct Register and struct Unregister and do something like
struct UserData
{
string Name;
int Age;
};
class Lab;
struct Register
{
string Name;
int Age;
Execute(Lab *pLab);
{
UserData st;
st.Name = Name;
st.Age = Age;
pLab->m_DBase.push_back(st);
}
};
struct Unregister
{
string Name;
int Age;
Execute(Lab *pLab);
{
...
}
};
class Lab
{
public:
vector<UserData> m_DBase;
Register m_Register;
Unregister m_Unregister;
};
Lab oLab;
oLab.m_Register.Name = "abc";
oLab.m_Register.Age = 50;
oLab.m_Register.Execute(&oLab);
Lot of scattered code...
better way is to use one\ single struct for all the related functionalities like Registering, Unregistering as well as storing the functionality specific data(as the case maybe).
Please refer:
http://www.codeproject.com/Messages/3539314/Re-My-vote-of-2-Not-quite-true.aspx[^]
|
|
|
|
 |
|
 |
I wasn't saying that at all - I was saying if you're collecting data into structures and there are common operations on those structures bind them together. Your discussion was all around currying data into functions. This even shows in the example you've given. If I'd have seen that I'd be asking what was the thing that had a name and age that the register and unregister function were taking.
class Lab
{
public:
int Register(string Name, int Age);
int Unregister(string Name, int Age);
};
makes me ask Name of what? Age of what? If you said they're the names and ages of students signing up for a lab course at a university:
class lab_course
{
public:
int register_student( string name_of_student, int age_of_student );
int unregister_student( string name_of_student, int age_of_student );
};
this is just saying "Oooo, missing abstraction, rewrite me! rewrite me!":
class student
{
public:
student( const std::string &name, unsigned age );
};
class lab_course
{
public:
int register( student to_register );
int unregister( student to_unregister );
};
If it's samples in a analytical lab or something else I can't imagine at the mo, you can follow the same pattern. The lab then acts on the student and doesn't maul about the raw bits of a student.
As a complete asside, you can now wrap registrations up into their own class if the fact of registration is important to you (say you wanted the scope of registration to be rollable back automatically in the face of an exception):
class course
{
public:
virtual int register( student to_register );
virtual int unregister( student to_unregister );
};
class lab_course : public course
{
public:
int register( student to_register );
int unregister( student to_unregister );
};
class course_registration
{
public:
course_registration( course *c, student *s ) : c_( c ), s_( s )
{
c_->register( *s_ );
}
~course_registration()
{
if( c_ && s_ ) c_->unregister( *s_ );
}
void finalise_registration()
{
c_ = 0; s_ = 0;
}
private:
course *c_;
student *s_;
};
and you'd use it along the lines of:
bool try_registration_for_course( course *c, student *s )
{
course_registration provisional_registration( c, s );
if( finalised ) provisional_registration.finalise();
}
This might be overkill for this type of system but for ones that have some financial sensitivity (or things like locking threading primitives) it can be really handy.
Cheers,
Ash
|
|
|
|
 |
|
 |
Initially, when I posted article my concern was just to keep together all the related external data on which a functions depends. In doing this I also found one little advantage that the function signature does not gets modified on modifying the internal data of struct. But, as I started getting feedback on article, one more point came in my mind that if there are many requirements which work on common data, for example:
In a Lab :
[1]one needs to register students,
[2]one needs to unregister students,
[3]keep information of students i.e maintain a database,
[4]print\ email\ display information of students.
then, its better to keep related data together so that the data can :
[a]be easily passed around collectively i.e "by Truck",
[b]easily be identified by a single name which specifies its purpose\ characteristic\ intension.
So in this case its better to use STUDENTDATA
struct STUDENTDATA
{
char Name[260];
int Age;
int RollNo;
};
class Lab
{
private:
vector<STUDENTDATA> m_Students;
public
Register(STUDENTDATA);
Unregister(STUDENTDATA);
PrintStudentInfo(STUDENTDATA);
EmailStudentInfo(STUDENTDATA);
};
instead of using(storing and passing to member functions) Name, Age and RollNo individually in class Lab.
Using STUDENTDATA gives a consistent look and feel.
Your suggestion was a perfect hit w.r.t using STUDENTDATA technique.(Thanks for digging into the matter )
class student
{
public:
student( const std::string &name, unsigned age );
};
class lab_course
{
public:
int register( student to_register );
int unregister( student to_unregister );
};
Here the related data Name, Age and RollNo can be used collectively through instances of class student which gives advantages like:
Aescleal wrote: [1]The lab then acts on the student and doesn't maul about the raw bits of a student.
[2]maintains that consistent look and feel.
[3]also various problems of data modifications(of members of class\ struct i.e Truck) can be avoided by encapsulating the related data inside Truck through member functions of class student.
And that is what this article was trying to suggest i.e pass the related data collectively i.e "by Truck".
Thanks for sharing your valuable thoughts.
|
|
|
|
 |
|
 |
In my eyes this article does not clearly enough state why it would be desirable to keep function signatures consistent in cases where the data passed to the function definitely has changed either in format or content.
Also, calling functions "by truck" requires a lot more setup work to be done:
1. Declare a variable of the right struct type.
2. Fill its members
3. Call the function.
Calling "by truck" can also lead to some hard to find errors. Users of the function might tend to keep a class scope struct variable at hand and just set the struct elements before calling the function. Unless you have enough discipline to adhere to rules like 'always assign to all struct members' or 'always use local variables for passing structs to functions' you might end up with old data in your struct variables.
Also, these rules have to be applied intellectually, whereas code changes required by changing a function signature are caught by the compiler.
|
|
|
|
 |
|
 |
Yes, this article was posted in order to discuss with the beautiful codeproject community,
[1]the pros and cons of pass by Truck method.
[2]in which all cases this method would be more appropriate.
etc,.
Marcus Schommler wrote: In my eyes this article does not clearly enough state why it would be desirable to keep function signatures consistent
[1]If there are many functionalities that are dependent on common data, then it might be better to keep such data in collective \cohesive manner. This will really provide consistent functionality signature.
Please refer:
http://www.codeproject.com/Messages/3539314/Re-My-vote-of-2-Not-quite-true.aspx[^]
[2] If function signature is consistent then, some more complex design can be achieved:
Please refer:
Converting a class into a CommandHandler.[^]
|
|
|
|
 |
|
 |
In your description of Truck passing you say that if one of the parameters gets changed the function signature stays the same. This is partly true but somewhat dangerous as the underlying structure is now different. So a caller using the old structure to a function relying on the new one will have mismatched parameters resulting in all sorts of difficulties.
It's time for a new signature.
|
|
|
|
 |
|
 |
That is one reason microsoft has the size as the first data member in many of the structures of the windows SDK.
|
|
|
|
 |