Kernighan and Ritchie (or K&R) and Other Coding Standards Discussion.

Started by
17 comments, last by kRogue 16 years, 11 months ago
Quote:Original post by ToohrVyk
Setters: I consider these to be prime indicators of bad design (not bad coding practices). Less than 1% of my classes need even a single setter.


I'm intrigued. Can you explain why it's bad design and also what the alternative is? (maybe give an example?) I use a lot of them so if I could be designing my stuff better, I'd love to know how.
Advertisement
Quote:Original post by Melekor
I'm intrigued. Can you explain why it's bad design and also what the alternative is? (maybe give an example?) I use a lot of them so if I could be designing my stuff better, I'd love to know how.


First, I would specify what a setter is. It's a member method in a class which is equivalent, in functionality and description (and possibly in implementation) to assigning the arguments to one or several member variables of that class, and which advertises to the users of the class the existence of those variables. So, in the following example, set_width and set_dimensions are setters:
class rect {  int w, h;public:  void set_dimensions(int w, int h) {      this -> w = w;    this -> h = h;  }  void set_width(int w) {    this -> w = w;  }};


On the other hand, functions such as std::vector<T>::resize are not setters, because they perform other operations in addition to altering a single value.

Now, why do I find setters to be an indicator of bad design? Because they only work if the class is a product type (that is, a list of properties which are more or less individually accessible). So, from there, I see two possibilities: first, the class provides nothing else, aside from that list of properties, at which point perhaps dropping the properties and making the class a structure with public members would achieve the same effect with less repetitiveness:

struct rect {  int w, h;  bool operator bool () const { return (w >= 0) && (h >= 0); }};


Or the class does something aside from providing those properties, at which point it violates the single responsibility principle, and I would suggest separating the product type functionality into one class (such as "configuration" or "options") and leaving the rest of the functionality in the original class.

Note that I did not say setters are bad design. They do have their uses, for instance when providing alternate ways of accessing properties (see, for instance, a mathematical vector class where subscripting is provided as an alternative). They are, however, indicators of bad design because they most frequently appear in such designs, for a simple reason: when the class was designed from the inside out. In object oriented programming, it is in general best to design the interface of a class first, and only then to write its implementation (altering the interface only if major issues prevent you from sanely implementing it). Setters, however, are property advertiser: "What is inside the class?" as opposed to the more relevant "What does the class do?" question.

My proposed alternative is to design the class in terms of usage. You almost never want to set the health of a character by a given amount; you usually want to inflict or heal a certain amount of damage. In fact, in my current game, I can spawn, own, kill, think, render, loop, schedule, place, damage, heal, stop, reset, order, send and direct, but the only thing I can set is the X, Y and Z coordinates of my vector (an eminent case of bad design, since I use subscripting anyway).

1) Nobody cares about how you format your code or name your variables unless you're working with other people;
2) And then you have a style guide that everyone follows, whose' rules are still arbitrary;
3) And you aren't, and don't, but your rules are still arbitrary;

To summarize : Nobody cares if you think having the { on the next line or the same line is better, or whether you break encapsulation on a whim, or name things number_of_elements instead of just elements.
Thanks ToohrVyk, that was very interesting, especially the bit about inside out vs. interface first design. I'll definitely be thinking about it a bit more before writing my next class. Also, with your definition of setter, 1% doesn't sound as far fetched as it did before :)
Quote:Note that I did not say setters are bad design. They do have their uses, for instance when providing alternate ways of accessing properties (see, for instance, a mathematical vector class where subscripting is provided as an alternative). They are, however, indicators of bad design because they most frequently appear in such designs, for a simple reason: when the class was designed from the inside out. In object oriented programming, it is in general best to design the interface of a class first, and only then to write its implementation (altering the interface only if major issues prevent you from sanely implementing it). Setters, however, are property advertiser: "What is inside the class?" as opposed to the more relevant "What does the class do?" question.

My proposed alternative is to design the class in terms of usage. You almost never want to set the health of a character by a given amount; you usually want to inflict or heal a certain amount of damage. In fact, in my current game, I can spawn, own, kill, think, render, loop, schedule, place, damage, heal, stop, reset, order, send and direct, but the only thing I can set is the X, Y and Z coordinates of my vector (an eminent case of bad design, since I use subscripting anyway).


Sometimes you want changing of data to have side effects: ie, people to be notified of the change, things that should automatically happen elsewhere, locks for multi-threaded purposes, etc. The wrapping of the data in settors and gettors gives you a guarantee that reads/writes to the data will be prefixed or followed with the other operations.

I'll admit most people don't do this -- but the advantage of having gettors/settors is that when you do start doing this (say, cacheing the height parameter, and only rebuilding it when it is both dirty and requested), the code that accessed the data doesn't have to change.

You can't do that with structs.
Quote:Original post by NotAYakk
Sometimes you want changing of data to have side effects: ie, people to be notified of the change, things that should automatically happen elsewhere, locks for multi-threaded purposes, etc. The wrapping of the data in settors and gettors gives you a guarantee that reads/writes to the data will be prefixed or followed with the other operations.


If the operation isn't just "change member variable X" (no locking, no notifications, no automatic execution), chances are that you shouldn't be using a setter or a public variable anyway. You are, after all, describing a set of semantics that are performed in addition to altering the variable.

If there is a sane meaning behind these, create an abstraction that does that operation (that abstraction should not, generally, be a setter).

Quote:I'll admit most people don't do this -- but the advantage of having gettors/settors is that when you do start doing this (say, cacheing the height parameter, and only rebuilding it when it is both dirty and requested), the code that accessed the data doesn't have to change.


Is that an advantage? First, altering code to adapt to a "transform a member variable into a function" change is a typical basic task for most post-2000 refactoring tools.

Second, if the objective of the class is not, precisely, to convey the height (and perhaps width) of an object in a "plain old data way", one has to wonder why the SRP was broken in the first place. And if it wasn't, then why not move the lazy evaluation to the function which creates the object?
Quote:Original post by ToohrVyk
If the operation isn't just "change member variable X" (no locking, no notifications, no automatic execution), chances are that you shouldn't be using a setter or a public variable anyway. You are, after all, describing a set of semantics that are performed in addition to altering the variable.

If there is a sane meaning behind these, create an abstraction that does that operation (that abstraction should not, generally, be a setter).


The technique can be used to present multiple "views" of the same data that is considered unencapsulated public data. For example, a complex number class might use either x,y or r, theta under the hood, and have get/set for *all four*, which work directly for two pairs, and for the other two pairs translate back and forth. This frees the calling code to use the instances as they'd like, and enables the programmer to profile to figure out which internal representation would work better and just change that. Voila, encapsulation-of-implementation of an "unencapsulated"-style interface. :)

(It can also be used, as also shown here, to expose sub-members of an aggregate without exposing details about how the aggregation is organized; but I suppose that benefit is pretty marginal.)

// I'm showing all the code in-line - except for the conversions ;) - just for// illustrative purposes.class Complex { // ignoring the existence of std::complex for now ;)  std::pair<double> xy; // or rtheta  std::pair<double> xy2rtheta(const std::pair<double>&);  std::pair<double> rtheta2xy(const std::pair<double>&);  // To avoid confusion, this class will only allow construction through its  // "named constructors".  Complex(std::pair<double> ab) : xy(ab) {}  public:  friend Complex Complex_xy(double x, double y) {    return Complex(std::make_pair(x, y));  }  friend Complex Complex_rtheta(double r, double theta) {    return Complex(rtheta2xy(std::make_pair(r, theta)));  }  void x(double x) { this->x = x; }  double x() { return x; }  void y(double y) { this->y = y; }  double y() { return y; }  void r(double r) {    std::pair<double> tmp(xy2rtheta(xy));    tmp.first = r;    return rtheta2xy(tmp);  }  double r() { return xy2rtheta(xy).first; }  void theta(double theta) {    std::pair<double> tmp(xy2rtheta(xy));    tmp.second = theta;    return rtheta2xy(tmp);  }  double theta() { return xy2rtheta(xy).second; }};// Changing this to use r-theta representation internally is tedious, but// straightforward (and would be anyway; there are so many interesting details// of the math operations, after all). And all the calling code is free to// pretend the values are in either format. While we don't get type-checking// to distinguish "created from xy" complexes from "created from rtheta" ones,// that might not be desirable anyway.


IMO this kind of thing is the real reason to use properties, in languages that support them (C# and Python). Unfortunately, the real world is full of typing-practice (and/or ctrl-c/v practice, and/or editor hotkey practice) code that uses them to reinvent the public data member.

Quote:Is that an advantage? First, altering code to adapt to a "transform a member variable into a function" change is a typical basic task for most post-2000 refactoring tools.


Even in C++? :)

Quote:Original post by RyanZec
I really don't think naming a variables shorter if it is not used as much is really a good idea. I mean K&R sytle would have a variables named nelems which if i where jsut to look at that, I would be a little confused and would not know what it mean right away but I would name the name variable number_of_elements and to me that is much more clear becuase i knwo right away that it is the number of elements(i may not know what elements it is referring to but I would just need to look at the code around quickly, alot wuicker then i would if i needed to figure out what nelems means).


Wouldn't it simply be named 'n'? Right there you should know that its a count of something. And as you say, if you want to know what its a count of.. you can simply look at the code.

Single-letter variable names for short lived variables are vastly superior to anything else IMHO.

As a simple case-in-point: 'n' for the count, 'i' for the index into those, and 'x' for the temp or result being produced.

Simple. Clear. Efficient screen usage.

As far as 'nelems' .. thats some quasi hungarian style with none of the (few) advantages and all of the drawbacks.


Quote:Original post by RyanZec
You guys disagree with me on any of these points, just like to know what you guys code standards are.


Well, as far as underscores within names and the like .. oh brother .. when do they actualy enhance readability? All they do is use extra space.

Member variables? Why are you messing with a class that you do not already know the member variables of? Seriously. And since such references are obviously out of scope of the function under consideration, shouldnt it automatically be assumed to be a member variable unless otherwise denoted? I mean really, are you going to assume that its a global variable or something?
I think member variabls should be denoted with something so you *know* fro sure when you are acesing them... the reason being that it alteast stops one irritating kind of scope bug, you jsut ahve to be bit by it once, to never want to be bit again....

though, the same line of though then should apply to member functions too. (some people like to write this->variable/this->function to avoid any ambiguity) but alas... I have to confess I do the following:

for member variable I give them the prefix m_ and for static memember variables I give them the prefix sm_, at times I get tempted to put into what I am working on now the prefix "f_" for member functions and "sf_" for static member functions... but doing that on over 1MB of source is not something I want to do with my time, and very few libraries for C++ put a prefix in front of member functions anyways (and typically all member variables are private anyways)... but still do get tempted... but ewwww... to go through everying just to do that... I don't know... if one takes it to exremes should local class and typedefs also have somekind of prefix like t_? (in the world of STL the answer is a big NO! as is prefixing functions with f_, because the convention for the function and local types does not have a prefix)

I never liked Hungrarian notation.

I never liked naming anything "foo" or "bar", that jsut irritates me to no end. The whole point of "foo" and "bar" is that they have no meaning, and if a paremeter (be it variable or template type) is used it *should* have a meaning.


Close this Gamedev account, I have outgrown Gamedev.

This topic is closed to new replies.

Advertisement