Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

have a method return a variable, or have it assign a value to a global variable? #87

Open
abrahamavnisan opened this issue Oct 14, 2013 · 6 comments

Comments

@abrahamavnisan
Copy link
Contributor

I'm confused about when I should have my methods return variables, as opposed to having my methods assign values to globally available variables. At least in certain cases, as in the example below, you can get the same result using both of these strategies, so which is better? Does the answer lie in the ever so mysterious passing by value / passing by reference?

Here is what I mean, broken down into a very simple example.

testApp.h:

#pragma once

#include "ofMain.h"

class testApp : public ofBaseApp{
    public:
        void setup();
        void update();
        void draw();

        void keyPressed(int key);
        void keyReleased(int key);
        void mouseMoved(int x, int y);
        void mouseDragged(int x, int y, int button);
        void mousePressed(int x, int y, int button);
        void mouseReleased(int x, int y, int button);
        void windowResized(int w, int h);
        void dragEvent(ofDragInfo dragInfo);
        void gotMessage(ofMessage msg);

    void voidMethod(int x);
    int intMethod(int x);
    int voidResult;
    int intResult;
};

testApp.cpp:

#include "testApp.h"

//--------------------------------------------------------------
void testApp::setup(){
    int x = 10;
    voidMethod(x);
    cout << "using voidMethod, result is " << voidResult << endl;
    intResult = intMethod(x);
    cout << "using intMethod, result is " << intResult << endl;

}

//--------------------------------------------------------------
void testApp::update(){

}

//--------------------------------------------------------------
void testApp::draw(){

}

//--------------------------------------------------------------
void testApp::keyPressed(int key){

}

//--------------------------------------------------------------
void testApp::keyReleased(int key){

}

//--------------------------------------------------------------
void testApp::mouseMoved(int x, int y){

}

//--------------------------------------------------------------
void testApp::mouseDragged(int x, int y, int button){

}

//--------------------------------------------------------------
void testApp::mousePressed(int x, int y, int button){

}

//--------------------------------------------------------------
void testApp::mouseReleased(int x, int y, int button){

}

//--------------------------------------------------------------
void testApp::windowResized(int w, int h){

}

//--------------------------------------------------------------
void testApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void testApp::dragEvent(ofDragInfo dragInfo){ 

}
//--------------------------------------------------------------
void testApp::voidMethod(int x){
    voidResult = x*2;
}
//--------------------------------------------------------------
int testApp::intMethod(int x){
    intResult = x*2;
    return intResult;
}
@bakercp
Copy link
Member

bakercp commented Oct 15, 2013

In this particular case, it doesn't make much sense to have methods that do things this way. In the case of intMethod you don't really need to return anything because the variable is already globally accessible to any other methods. The voidMethod() style method could be useful in some cases, but the simple cases you have outlined here don't really demonstrate a specific use case, so it's difficult to say ...

In terms of "principles" to follow (I'm sure there are official names for these principles, but I'll just give you my names ...):

  1. [Encapsulation](http://en.wikipedia.org/wiki/Encapsulation_(object-oriented_programming\)) : the principle that suggests that class member variables and member functions should only be available on an "as needed" basis. This is particularly important when sets of variables must be modified simultaneously to achieve certain results. In this case, allowing a user to modify one of the interdependent variables alone could place the system in an undefined state. Check out the wikipedia article for a "principled" approach.
  2. Naming is important: Well-named member methods/functions should suggest their behavior. Thus, if you call a function getNumberOfWidgets() one can assume that the return type will be some kind of integral type (int, unsigned int, long etc.). You wouldn't expect it to be void because of the verb get in the method name. In most contexts you would also assume that you can't have a "piece" of a widget (which might suggest that a partial widget could be returned via a float or a double). In contrast, a function like getPercentWidgetsFilledWithBeans() might suggest that the return type could be a float (like 99.2 or 0.992). The point is, well-named functions (like well-designed real-life objects) should suggest their Affordances. You will be better served by creating example code that is less abstract and more specific. This is a case where it is difficult to come up with a solid rule for all situations ...

@abrahamavnisan
Copy link
Contributor Author

Thanks, @bakercp! I read over your response and the wikipedia articles, both of which were helpful in beginning to answer my question, but perhaps more importantly in understanding why posting such abstract code isn't all that helpful! So please consider the point taken (and sorry about that.) I am going to build out, clan up and comment the example I'm actually working on and post it here, hopefully tonight.

@bakercp
Copy link
Member

bakercp commented Oct 15, 2013

perfect :)


http://christopherbaker.net

On Mon, Oct 14, 2013 at 9:54 PM, Abraham Avnisan
[email protected]:

Thanks, @bakercp https://github.com/bakercp! I read over your response
and the wikipedia articles, both of which were helpful in beginning to
answer my question, but perhaps more importantly in understanding why
posting such abstract code isn't all that helpful! So please consider the
point taken (and sorry about that.) I am going to build out, clan up and
comment the example I'm actually working on and post it here, hopefully
tonight.


Reply to this email directly or view it on GitHubhttps://github.com/bakercp/ExperimentalMedia/issues/87#issuecomment-26304027
.

@mikewesthad
Copy link
Contributor

Hey @abrahamavnisan, does this code get at your point? Again, it is meaningless code, but maybe have two alternative versions of code tackling the issue will help. The question is whether there is a general principle that would indicate to you whether the int variable beans should be globally defined (i.e. a class variable of testApp) or locally defined (to the update() method of testApp).

beans is a variable local only to the update method:

class testApp{
    public:
        void setup();
        void update();
        void draw();
        int calculateNumberBeansFromMousePosition(int x, int y);
        vector <ofVec2f> beanPositions;
};

void testApp::setup() {
}
void testApp::update(){
    beanPositions.clear();
    int beans = calculateNumberBeansFromMousePosition(mouseX, mouseY);
    for (int i=0; i<beans; i++) {
         // Do whatever to generate beans and add their positions to the vector
    }
}
int testApp::calculateNumberBeansFromMousePosition(int x, int y){
    return x*10.0f/y;  // Or do whatever magic to calculate the number of beans
}
void testApp::draw(){
    for (int i=0; i<beanPositions.size(); i++) {
         // Do whatever drawing
    }
}

versus

beans is a class variable and can be accessed anywhere within testApp:

class testApp{
    public:
        void setup();
        void update();
        void draw();
        void calculateNumberBeansFromMousePosition(int x, int y);
        vector <ofVec2f> beanPositions;
        int beans
};

void testApp::setup() {
}
void testApp::update(){
    beanPositions.clear();
    calculateNumberBeansFromMousePosition(mouseX, mouseY);
    for (int i=0; i<beans; i++) {
         // Do whatever to generate beans and add their positions to the vector
    }
}
int testApp::calculateNumberBeansFromMousePosition(int x, int y){
    beans = x*10.0f/y;  // Or do whatever magic to calculate the number of beans
}
void testApp::draw(){
    for (int i=0; i<beanPositions.size(); i++) {
         // Do whatever drawing using the vector of positions
    }
}

I'll just add my two cents in addition to what Christopher has said. Encapsulation is the "proper" way to do things. You should only have global class variables when the variables are actually intended to be available anywhere within the class, like the velocity/position/acceleration of our particle classes. Otherwise, your variables should be local. Some reasons why:

  • Global variables can complicate code because they can be accessed anywhere within the class, so someone reading your code has to hunt around to find everywhere the variable is changed in order to track your logic.
  • If you were to collaborate on writing a giant class with someone else, you would probably want to do as much encapsulation of your variables into local scopes as possible. That way you don't have to worry about someone else's function modifying the values of variables you are using within one of your functions.
  • There's probably an argument for making as much of your code into independent modules as possible. That way you could potentially break up pieces of it into separate threads without worrying that the threads will try to modify the same memory location (i.e. the global variable).

But, global variables can make the writing your code go faster sometimes... You wouldn't have to remember to pass and return 'beans' to and from your various functions. When I'm trying to sketch out some code really quickly to see if any idea is worth exploring, the number of global variables I have defined explodes. Then l clean it up later. There have to be better examples to explain this side of it, but I'm blanking right now.

So encapsulation is a good ideal - especially if it improves readability and robustness - but sometimes you temporarily (or permanently) bend the rules. I think this article echoes these points. I'm sure there must be plenty of discussions on stackoverflow global vs local scoping for variables.

@mikewesthad
Copy link
Contributor

Or I should add, at least that's my experience, which very well could be silly and biased.

@abrahamavnisan
Copy link
Contributor Author

Thanks so much for the detailed response, @mikewesthad! I've been busy making the code that the question is actually based on presentable but the quick look I took at it was super helpful. I'm looking forward to taking a closer look tomorrow, after some sleep! Hopefully throwing the actual code into the mix will help and not hinder the discussion.

I've uploaded the code in question here - it might or might not evolve into an of poco/regex tutorial - please take a look and let me know what you think! There's an "intro" in the .h file comments, and there are comments throughout.

Also, @bakercp, I finally (I hope!) mended the damage I had made on my repo, so take a look at the pull request when you have a minute and see if it looks OK to accept - I think it should be. (ps, @mikewesthad, you were totally right when you recommended I NOT use git rm -r -f Hahaha, I guess it was kind of obvious? Well, I learned my lesson! (And learned a little more about git in the process.))

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants