views:

904

answers:

22

When writing code, especially when dealing with dates and times, you have to work with a lot of specific numbers, eg: 60 seconds in a minute, 3600 = seconds in an hour.

Some people stick to using the raw values for many of these, whereas others put them into constants to increase readability.

eg:

$x = time() + 3600;
$y = time() + 86400;
$z = time() + 604800;

// vs

define('MINUTE', 60);
define('HOUR',   60 * MINUTE);   // 3600
define('DAY',    24 * HOUR);     // 86400
define('WEEK',    7 * DAY);      // 604800

$x = time() + HOUR;
$y = time() + DAY;
$z = time() + WEEK;

Of course, the second one is easier to read, but slightly OTT for some of the lower values, so where exactly do you draw the line? Personally, I see no problem with the readability of 86400 (in my head I automatically read that as "24 hours"), but would draw the line at the WEEK constant.

A: 

I would say HOUR, MINUTE and DAY are fine. Going more granular doesn't seem to provide much more readability.

zodeus
+1  A: 

I would draw the line based on the size of the project. The larger the project, the more abstractions and constants... As simple as that.

Subtwo
+16  A: 

One of my professors once told us not to put any magic numbers in our code except 1, -1, and 0. That's a little extreme, but it sticks in my mind and still guides me although I don't adhere to it completely.

In your example, I would prefer the symbolic names in all cases.

Craig S
No, that's not extreme enough. All numbers should be defined as constants. In fact even the definitions shouldn't use constants, I'd use "#define ONE one" if possible, so that there are **no** naked numbers in the code :-)
paxdiablo
And "#define one 1", too?
Nikhil Chelliah
I have heard an outsourcing nightmare story that relates. The word came down on them to eliminate all magic numbers in the code. The outsourced team created a huge header file containing nothing but "#define INT_1 1 #define INT_2 2", all the way up to 1000.
James Van Huis
Hilarious. It always pays to try to remember common sense, doesn't it?
Craig S
+4  A: 

My approach is to not used named constants, but keep the units separate like this:

long twelvePM = 12 * 60 * 60 * 1000L;
long timeout = 60 * 1000L;

This way it's clear that this are in milliseconds and they are easy to adjust in case I want to change values later.

Outlaw Programmer
long twelvePM = 12 * HOURS_IN_MS;long timeout = 1 * MINUTES_IN_MS;
FryGuy
That's the way I prefer it too. It is pretty obvious what it means for time and one should note that time is making up 99% of this multiplication stuff anyway. (Yes, I do realize this is not very empiric ;-))
Caffeine
+8  A: 

I'd go constants (or some cutesy derivative, like Rails' 15.minutes convention) pretty much everywhere. For me, it's about simplifying the "typing" of it all; if I see "10 * MINUTES" somewhere in a line, I know I'm dealing with time (or someone's up for an arse-kicking). If I see 10 * 60 or 600 it's entirely possible that I might not grok that we're dealing with time quite so easily.

womble
That's the same as hungarian notation: It's fixing a problem which schouldn't be there in first place. Code should be self-explainatory and names should be meaningful which pretty much eliminates the need for long comments and constants. The only valid reason to use constants to me is "D.R.Y.".
Caffeine
@Caffeine, What is hungarian notation in @womble's post?
strager
It's a coding convention for naming variables heavily used by Microsoft (http://en.wikipedia.org/wiki/Hungarian_notation). It feels to me that if you can't figure out what type a variable has, something has gone horribly wrong.
Caffeine
I mean to ask, where does he mention or use it? I know what it is.
strager
@Caffeine: Please learn the difference between app hungarian and systems hungarian. Oh, and check out http://www.joelonsoftware.com/articles/Wrong.html
womble
@womble, I agree ... I had no clue what he was talking about.
strager
@womble: Microsoft made the bad version of Hungarian notation so popular, there is no need to differentiate. Its forever marked as one of the stupidest things, no matter how good the original author's intent was.@strager: Misread your post, but yea I meant the Microsoft-we-suck-real-bad version.
Caffeine
@Caffeine: Hence "Apps" vs "Systems" hungarian, to differentiate. Apps hungarian has definite value, and disregarding it out of hand just because Microsoft are idiots shows a distinct lack of willingness to consider best practices.
womble
+2  A: 

I tend to use constants over magic numbers almost exclusively. I think it increases readably and gives you one point in the program to fix any errors. There are several magic '60's, for example: 60 seconds in a minute, 60 minutes in an hour.

Trent
A: 

A day is always going to have 24 hours, and an hour will always be 60 minutes.

I think the point to naming them as descriptive constants is where a) the numeric value isn't clear (604800?) b) the value may someday change

MikeW
+7  A: 

There is a relatively good argument then any number other than zero or one should be a named constant. Even in your example where you assert that you have no problem with the readability of 86400, there is still some ambiguity about what your unit of measure is.

If I were maintaining your code, I'd prefer to see named constants like:

const int secondsInDay = 86400;

.. not a lot of ambiguity there. :) Depends on whether anyone (including yourself.. I mean, I struggle to remember what I wrote last week, let alone last year!) will be required to maintain your code at some stage.

Scott Ferguson
No ambiguity, just a big fat *bug*. (grin) But it demonstrates the power of constants quite well -- if you were using magic numbers nobody would know if you meant to get the seconds in an hour or seconds in a day... bad bug to find.
womble
You can't afford to assume that nobody else will be maintaining your code. That somebody could be you, months later, having mostly forgotten how your code works. :)
Parappa
haha, womble, you're dead right and excellent point.. (bug now fixed).
Scott Ferguson
@Parappa yes of course, you're exactly right. I'll ammend my answer to take that into consideration.
Scott Ferguson
Call it secondsInAnEarthDay and I'm in...
Jason Punyon
wholeSecondsInACalendarEarthDay_notIncludingDaylightSavingsAdjustingDaysOrLeapSeconds
James Van Huis
+3  A: 

I tell my students this:

If you can read the code without comments then there is no need for constants. If you have to have explain the code then it needs comments.

I also tell them:

Thinking is bad. In that if you write code that makes people have to dig through it to understand it that is not a good thing.

So, if you can show the lines to a co-worker and they can understand it without constants you are (probably) good without them. Likely you will want the constants.

TofuBeer
+2  A: 

I keep in simple; the name of the variable and the comment (if needed in case of very magical numbers) will be sufficient to pass code review.

int someDelay = 1232323; // in milliseconds.
Max
A: 

Personally, I like to have the named constants in case I have to change the value of the constant then I only have to do it in one place.

Casey
yea. hoursPerDay=25; // muhahahaha!
bendin
+36  A: 

86400 is not ok, since you can easily mistype it as 84600, 88400, etc

A mistyped constant will be a compile error

Pyrolistical
I love to see logic and reasoning come into what was such a subjective question. Top effort!
nickf
I can mistype 60 as well (I'm still not as good two rows from the home row as I should be). 50 looks as much like the number of seconds in a minute, to me, as 84600 like the number of seconds in a day.
David Thornley
A: 

I'd definitely use constants, while you can look at 86400 and as 24 hours, a future programmer doing maintenance might not be so bright and will be scratching his head on what exactly that number means. This is also the case for you if you come back one day and have forgotten what 86400 means.

alex
A: 
$x = time() + HOUR;
$y = time() + DAY;
$z = time() + WEEK;

How about magic strings? I tend to do things like this:

  $x = strtotime('now + 1 hour');
  $y = strtotime('now + 1 day');
  $z = strtotime('now + 1 week');

Yes, it probably runs fractionally slower. But in the greater scheme of things, does it really matter?

Ant P.
Performance might not be relevant. But Strings cannot be checked by the compiler and that's bad if an alternative with compile time checking is available as shown in the other examples.
Bananeweizen
+4  A: 

Personally I'd use SECS_PER_MIN, SECS_PER_HOUR, etc. I've even been known to use NANOS_PER_SEC on occasion. I would always do that if a language lacked scientific notation for integer literals.

It's not about readability, exactly. The reason for using SECS_PER_DAY rather than 86400 is not just to do with what general knowledge we expect of the reader. It's about self-documenting code, which means unambiguously showing your working.

Sure, everyone knows there are 60 seconds in a minute. But they also know there are 60 minutes in an hour. If you use a literal 60, it's probably obvious what your code is intended to do. But why take the chance?

If you use SECS_PER_MIN, then it is definitely obvious that you're converting a time in minutes (just 1 minute, in your example) to a time in seconds. You are not, for instance, adding one hour to a time measured in minutes, or one degree to an angle in minutes. You are not adding 5 feet to a length measured in inches.

Named constants provide more context for the surrounding code. For your example of adding a time, we know just by looking at one line that $x needs to be a time in seconds. The name of the constant reiterates that $x is not a time in millis, or clock ticks, or microfortnights. This makes it easier for everyone to check and maintain correctness of units: they can tell by looking that what you intended to do is what you actually did. They never have to even entertain the notion that you intended to add 60 millis to a time measured in millis, but got it wrong because $x was actually in seconds.

Named constants also help avoid typos. Sure, everyone knows there are 86400 seconds in a day. It doesn't necessarily follow that they won't typo this as 84600, or that they will immediately spot the error if someone else has. Granted you have complete testing, so such an error would never make it into the field, but the most efficient way to fix it is to prevent the faulty code making it to test in the first place. So I'd define the constants like this (or whatever syntax):

SECS_PER_MIN := 60; 
SECS_PER_HOUR := 60 * SECS_PER_MIN;
SECS_PER_DAY := 24 * SECS_PER_HOUR;
SECS_PER_WEEK := 7 * SECS_PER_DAY;

Or, if the other constants were needed anyway (which in the case of time they probably wouldn't because you normalise everything into secs at the first opportunity to reduce the chance of confusion):

SECS_PER_MIN := 60;
MINS_PER_HOUR := 60;
HOURS_PER_DAY := 24;
DAYS_PER_WEEK := 7;

SECS_PER_HOUR := SECS_PER_MIN * MINS_PER_HOUR;
etc.

Note the order on the RHS: the minutes visibly "cancel", making the working even more clear. Not such a big deal with time, but it's good to establish a consistent scheme early, so that when things get nasty later (CLOCKS_PER_SEC, PLANCK_LENGTHS_PER_PARSEC) you can get it right using familiar techniques.

Steve Jessop
There's also 60 seconds in a minute, and 60 minutes to a degree, and 360 degrees to a circle. Such terms as "hour" or "degree" will keep things straight even in a geometry or geography app.
David Thornley
+2  A: 

For use in units like this, a good trick is to name all the units (even the base), that way your code reads like a properly specified measurement:

// The base unit of time is the second
const double second = 1.0;
const double ns = 1e-9 * second;
const double micros = 1e-6 * second;
const double ms = 1e-3 * second;
const double minute = 60.0 * second;
const double hour = 60 * minute;
const double day = 24 * hour;
const double week = 7 * day;
const double year = 365.24 * day;

then always use the appropriate unit in your code

// Set up a 90 second timeout
timeout(90*second);

or

elapsedDays = floor(elapsedtime / day);

You see this formulation in various scientific packages (for instance Geant4) from time to time.

dmckee
Stop!Why are you using double to represent something as important as time? Use an integer type like long, or bignum if that isn't big enough. You will run into floating point issues
Pyrolistical
Because these are *measurements* and they don't have indefinitely high precision in anycase. With a double you can lose circa 10 decimal digits precision and still be consistent with a pretty damn good measurement. Ditto for distances, energies etc. See Geant4, and ask again.
dmckee
And they run fast which bignums don't and the have a huge dynamic range which fix-lengths integer type don't.
dmckee
regardless of the precision argument the concept is good
TofuBeer
Also in the sample code, it makes it easy to divide and not have your decimal digits drop off due to not casting correctly.
Robert Wagner
A: 

I may be asking for downvotes here, but I believe none of your examples to be magic numbers. They are well defined universal constants that you'll never have to substitute with something else.

My approach is a mix of some of the previous answers:

// properly named variables
int daysBeforeFriday = 4;

// expanded math expressions
int minutesBeforeFriday = 4 * 24 * 60;

// 4 days in seconds (clarification comments)
int secondsBeforeFriday = 4 * 24 * 60 * 60;

// etc..

I would, however, precompute certain expressions within a constant in cases where the values involved are not obvious or optimization and/or repeatability are concerns (floating point values and their rounding errors, for example).

Pardon me for using C as an example of this last case, but look here to understand what I mean. ;)

Jorge Alves
A: 

I use named constants

  • If the number is going to appear in several places
  • If the number might ever change (new configuration, new project)

Sometimes I embed the number, as a readability hint.

#define LPP57   57     // Lines per page

etc.

gbarry
I'd suggest also where the meaning might be unclear. If the number 47 appears only once in your code, and will never change, I'd still rather see why it's 47.
David Thornley
I think I would rather see 57 than LPP57. Both suck, so just pick the easier of the 2. Why is it so hard to type out the whole phrase so it can be read?
Dunk
A: 

It's important to readability that your constants include the units of measure. Otherwise

$x = time() + HOUR;

is scarcely any better. How do we know that $x, the time() function and the constant HOUR are all numbers in units of seconds? Well, we can't rename the time() function, but we can at least write

$x_seconds = time() + SECONDS_PER_HOUR;

and now it's clear, with no need for commenting.

For that matter, how do we know you're not doing a string concatentation, ie appending the suffix "am" or "pm". You might say "well, no one in their right mind would call that HOUR" but I'm not so sure.

Some folks advocate Hungarian notation for this purpose. I don't like the form of Hungarian that decorates names with their underlying type (int, handle, etc) but I do like decoration with the semantic type (seconds).

Jim Davis
A: 

Work at the highest level of abstraction available.

Ruby ActiveSupport Example

3.months.ago
1.year.from_now

The same principle can be applied to other less flexible languages as well, Java will allow you to do something like:

TimeHelper.months().ago(3);

If you don't have objects in your language you can do this:

$x = strtotime('now + 1 hour');
$y = strtotime('now + 1 day');
$z = strtotime('now + 1 week');

A lot less room for misinterpretation. Why fiddle with things below the problem domain if you don't have to?

Daniel X Moore
well not every language has such niceties, so there is a reason to use one or the other.
nickf
That doesn't mean that you can't apply the same principles, even in Java you can do something like TimeHelper.months().ago(3) and have it return whatever makes sense to add to your date/time.
Daniel X Moore
"Why waste time fiddling with things so far below the problem domain if you don't have to?" I wouldn't consider adding seconds outside or below the problem domain of working with dates...
nickf
How many calendar applications have you specify your appointments to the second?
Daniel X Moore
A: 

It depends a bit on the language, but I would create a type. That way you will be able to read the intention of the code as well as having the type checking system verify some of that intention at compile time and/or runtime.

The constants are hidden inside the relevantly named methods, and not repeated (you can unit test the type/constants if you want to).

Of course, there may be some optimisation/performance issues with this approach in some cases. But in my experience they have rarely mattered.

e.g. for illustration something like:

class Time
{
   private long time;

   Time(long hours, long minutes, long seconds, long milliseconds) { ... }
   Time(long milliseconds) { ... }

   Time addMilliseconds(long increment)
   {
      return new Time(time + increment));
   }

   Time addSeconds(long increment)
   {
      return addMilliseconds(increment * 1000);
   }

   Time addMinutes(long increment)
   {
      addSeconds(increment * 60);
   }       

   Time addHours(long increment)
   {
      addMinutes(increment * 60);
   }

   [...]

   long getTimeInMilliseconds() { ... };
   long getTimeInSeconds() { ... };
   long getTimeInMinutes() { ... };

   [.. maybe some static factory methods ...]
}

Which can be used like this:

Time oneHourAhead = new Time(now()).addHours(1);
Time tenMinutesAgo = new Time(now()).addMinutes(-10);
Time oneHourTenMinutesAhead = new Time(now()).addHours(1).addMinutes(10);
flamingpenguin
+2  A: 

My rule of thumb when teaching (so, more hard core rules than in real life) was any number other than -1, 0, 1, or 2 that was used more than once NEEDED to be a constant. IF you use it only once, you can just comment it if you prefer...

Brian Postow