Register a SA Forums Account here!
JOINING THE SA FORUMS WILL REMOVE THIS BIG AD, THE ANNOYING UNDERLINED ADS, AND STUPID INTERSTITIAL ADS!!!

You can: log in, read the tech support FAQ, or request your lost password. This dumb message (and those ads) will appear on every screen until you register! Get rid of this crap by registering your own SA Forums Account and joining roughly 150,000 Goons, for the one-time price of $9.95! We charge money because it costs us money per month for bills, and since we don't believe in showing ads to our users, we try to make the money back through forum registrations.
 
  • Post
  • Reply
Bhaal
Jul 13, 2001
I ain't going down alone
Dr. Infant, MD

ShoulderDaemon posted:

<snip>

If you have complex resource building that can fail at multiple points and requires multiple cleanup, it's probably not a good idea to try and house the whole drat thing in one scope. Here's that same logic broken into functions. It has some early returns but if that's an issue we all know ways to solve it, I'm just being lazy.
code:

void build_resource(...)
{
  // ...

  if ( ... ) 
  {
    // ...

    if ( ... )
    {
      cleanup1();
      return;
    }

    // ...
  };

  build_2nd_stage(...)
}

void build_2nd_stage(...)
{
  // ...
  if ( ... ) 
  {
    // ...
  };

  // ...

  if ( ... )
  {
    cleanup2();
  }
  else
    build_3rd_stage(...);
}

void build_3rd_stage(...);
{
  // ...

  if ( ... )
  {
     cleanup3();
     return;
  }

  // ...
}

void cleanup1()
{
...
}

void cleanup2()
{
...
cleanup1();
}

void cleanup3()
{
...
cleanup2();
}
Obviously you can have much more meaningful function names, but with this you can more easily abstract the surrounding logical branching of where you're at and just focus on one segment at a time. (This would also be one of those times to write a function that you know will only ever get called in a single place).

Adbot
ADBOT LOVES YOU

chips
Dec 25, 2004
Mein Führer! I can walk!

Bhaal posted:

If you have complex resource building that can fail at multiple points and requires multiple cleanup, it's probably not a good idea to try and house the whole drat thing in one scope. Here's that same logic broken into functions. It has some early returns but if that's an issue we all know ways to solve it, I'm just being lazy.

Obviously you can have much more meaningful function names, but with this you can more easily abstract the surrounding logical branching of where you're at and just focus on one segment at a time. (This would also be one of those times to write a function that you know will only ever get called in a single place).

If stage 3 construction fails, how does stage 2 cleanup happen? I suppose

code:
if ( ... || !build_3rd_stage(...))
  {
    cleanup2();
  }
Works, right?

floWenoL
Oct 23, 2002

Bhaal posted:

If you have complex resource building that can fail at multiple points and requires multiple cleanup, it's probably not a good idea to try and house the whole drat thing in one scope. Here's that same logic broken into functions. It has some early returns but if that's an issue we all know ways to solve it, I'm just being lazy.

Obviously you can have much more meaningful function names, but with this you can more easily abstract the surrounding logical branching of where you're at and just focus on one segment at a time. (This would also be one of those times to write a function that you know will only ever get called in a single place).

How very fitting that this was posted in the Coding Horrors thread!

(I still can't decide whether you're trolling or not.)

Dijkstracula
Mar 18, 2003

You can't spell 'vector field' without me, Professor!

floWenoL posted:

(I still can't decide whether you're trolling or not.)
There's no way he couldn't be.

Bhaal
Jul 13, 2001
I ain't going down alone
Dr. Infant, MD
Breaking monster functions into component parts is a horror? Obviously I wouldn't chop things into nonsense enumerated functions like in the example, and it's ridiculous to think this applies to every function. But the horror is having a 1000 line member function that (through various points) recurses and/or calls the same function on different instantiations. Oh, and it can bail out along the way, causing no significant changes, or partial, or full. I came across that one about a year ago, and found myself having dreams at night about the nested, branching mayhem that I'd spend all day trying to step through.

Look at the way "break;" can be viewed as a limited and not-very-descriptive form of goto. Most of the time that doesn't matter, and using a goto instead would be useless. But there are times where it isn't sufficient enough to describe the where, what, and why that something like "goto cleanup;" can give us.

Similar to that, look at viewing a segment of conditionally-nested code and keeping the conditions in mind, and what needs to happen if a necessary condition fails and you need to back out. Usually isn't that big of a deal, and you just have this nice little segment of code that branches around a bit. But if the nesting is particularly complex, or the bail-out procedure differs frequently from one set of conditions to the next, that "nice little segment" might start earning a reputation among anyone who has to deal with it. To add more description to what's going on (and flexibility to what you can do), you break the logic it into functions that make sense.

"Let's see, so right here foo is false and our container wasn't empty and X is null but Y isn't, and Y's depth is greater than l_depth, so if GetVar() fails here...."

Turns into

"I'm in fillContY(true), which means we have a non-empty container (because we were passed true) that we're first flushing then filling via Y only, and if GetVar() fails we abort and return false, which is handled by the caller based on foo and Y's depth versus l_depth, but i don't really care about that part right now"

baquerd
Jul 2, 2007

by FactsAreUseless

Bhaal posted:

Breaking monster functions into component parts is a horror?

When you nest them like you did in your example so the horror is only magnified, yes. If you had a single logic train function calling other functions, it would look good and be much more readable (disclaimer about function names applies, and I wouldn't separate the cleanup code, but this is still worlds better than your example):

code:

void build_resource(...)
{

  build_1st_stage();

  if ( ... ) {
    cleanup1();
    return;
  }

  build_2nd_stage(...)

  if ( ... ) {
    cleanup2();
    cleanup1();
    return;
  }

  build_3nd_stage(...)

  cleanup3();
  cleanup2();
  cleanup1();

}

Zombywuf
Mar 29, 2008

code:
enum {
    NO_CLEANUP,
    CLEANUP1,
    CLEANUP2,
    CLEANUP3
} cleanup_mode = NO_CLEANUP;
while (1) {
    switch (cleanup_mode) {
      case NO_CLEANUP:
        ...
        if (...) {
            cleanup_mode = CLEANUP1;
            continue;
        }
        ...
        if (...) {
            cleanup_mode = CLEANUP2;
            continue;
        }
        ...
        if (...) {
            cleanup_mode = CLEANUP3;
            continue;
        }
        break;
      case CLEANUP3:
        ...
      case CLEANUP2:
        ...
      case CLEANUP1:
        ...
    }
}
I call it Inverse Duff's Device.

TSDK
Nov 24, 2003

I got a wooden uploading this one
Jesus H Wept...
code:
void ProcessResources()
{
    int resource1 = INVALID;
    int resource2 = INVALID;
    int resource3 = INVALID;

    if ( AcquireResources( &resource1, &resource2, &resource3 ) )
    {
        // Do processing
    }

    ReleaseResources( &resource1, &resource2, &resource3 );
}

int AcquireResources(
    int *p_resource_1,
    int *p_resource_2,
    int *p_resource_3 )
{
    *p_resource_1 = GetResource( ... );
    if ( *p_resource_1 == INVALID ) return FALSE;

    *p_resource_2 = GetResource( ... );
    if ( *p_resource_2 == INVALID ) return FALSE;

    *p_resource_3 = GetResource( ... );
    if ( *p_resource_3 == INVALID ) return FALSE;

    return TRUE;
}

void ReleaseResources(
    int *p_resource_1,
    int *p_resource_2,
    int *p_resource_3 )
{
    if ( *p_resource_3 != INVALID ) ReleaseResource( ... );
    if ( *p_resource_2 != INVALID ) ReleaseResource( ... );
    if ( *p_resource_1 != INVALID ) ReleaseResource( ... );
}
You're essentially doing three things, so decompose the problem into three steps. Don't try to use your ultra leet indenting, break, goto if-while-else ninja skills to compress those three things into a single function*.


[*] There are exceptions, obviously, but if you don't know when an exception would be appropriate, then you're not good enough to be making an exception. Write clear code first and foremost.

pseudopresence
Mar 3, 2005

I want to get online...
I need a computer!
code:
with GetRes1() as r1, GetRes2() as r2, GetRes3() as r3:
    ...
:smug:

TheSleeper
Feb 20, 2003

TSDK posted:

You're essentially doing three things, so decompose the problem into three steps. Don't try to use your ultra leet indenting, break, goto if-while-else ninja skills to compress those three things into a single function*.


[*] There are exceptions, obviously, but if you don't know when an exception would be appropriate, then you're not good enough to be making an exception. Write clear code first and foremost.

Yea, man, it's totally unclear that the code does processing only if it acquires all three resources, but always releases any resources it gets.

Edit: vvv yes, I think I do :doh:

TheSleeper fucked around with this message at 21:50 on Jul 28, 2009

Mustach
Mar 2, 2003

In this long line, there's been some real strange genes. You've got 'em all, with some extras thrown in.
Do you need a nap or something? That is his own example of doing what he's recommending.

CoasterMaster
Aug 13, 2003

The Emperor of the Rides


Nap Ghost
During a code review, we stumbled across something like this:

code:
public int doSomething(int foo, int bar) {
  /* some processing goes here */
  return doSomething2(someTemp);
}

private int doSomething2(int someTemp) {
  /* some more processing goes on here */
  return doSomething3(someOtherTemp);
}

private int doSomething3(int someOtherTemp) {
  /* even more code is here */
  return someValue;
}
Some people really take that 'no long functions' rule to heart. The variable names and functions names are changed, but the concept is still there.

ZorbaTHut
May 5, 2005

wake me when the world is saved

CoasterMaster posted:

Some people really take that 'no long functions' rule to heart. The variable names and functions names are changed, but the concept is still there.

I had a 3000-line function once, though I later split it up into a pair of 1500-line functions and some support functions. It was a very long complicated algorithm with a lot of unique steps and a painful amount of state transmitted between all of them.

Whenever I talk about it, people insist that I should have split it up into 50-line functions, which would have required either about 20 parameters for each function or a struct containing 20 members that were only related by virtue of being the entire state of the algorithm.

"But 1500 lines is just too long! How can you ever understand it?" drat sight better than I can understand thirty 50-line functions strewn about my codebase.

Bhaal
Jul 13, 2001
I ain't going down alone
Dr. Infant, MD
Obviously breaking up a large/complex function is more of a useful tooling (usually when refactoring) to keep in mind and less of a universal mandate that kicks in when a function reaches a certain line count.

Monster functions usually have several "steps" to them and those steps can vary on their interdependence with eachother. If cleaning up the readability is the goal and the dependencies between areas of the function are simple enough, then you can often find logical ways to subdivide the labor that might not have been considered before. That makes it easier to follow along the process, as a function call (instead of a block of code) is easier to abstract out if you aren't concerned with it, etc.

If changing scope or whatnot causes more problems than it solves then of course it's probably not the appropriate thing to do.

huge sesh
Jun 9, 2008

code:
buf.putByte((short)1);
I think he's worried about Java's lack of unsigned types but that sure is an awkward way to word it.

Zombywuf
Mar 29, 2008

ZorbaTHut posted:

I had a 3000-line function once, though I later split it up into a pair of 1500-line functions and some support functions. It was a very long complicated algorithm with a lot of unique steps and a painful amount of state transmitted between all of them.

Whenever I talk about it, people insist that I should have split it up into 50-line functions, which would have required either about 20 parameters for each function or a struct containing 20 members that were only related by virtue of being the entire state of the algorithm.

"But 1500 lines is just too long! How can you ever understand it?" drat sight better than I can understand thirty 50-line functions strewn about my codebase.

You are a bad programmer and should feel bad.

Sagacity
May 2, 2003
Hopefully my epitaph will be funnier than my custom title.

ZorbaTHut posted:

a struct containing 20 members that were only related by virtue of being the entire state of the algorithm.
As opposed to a struct containing...what, exactly?

Fullets
Feb 5, 2009
Now, I'm no Perl guy, but why would someone write
code:
package Blah::Config;
...
my @defaults = (host => '127.0.0.1',
                port => 9400,
                # many, many more key => value pairs
                );
my %defaults = @defaults;
while (my ($key, $value) = each %defaults) {
    ${"Blah::Config::$key"} = $value;
}
when they clearly know how our works, as they're happy to use it for some of the other package scalars they introduced in that commit.

Mata
Dec 23, 2003

CoasterMaster posted:


Some people really take that 'no long functions' rule to heart. The variable names and functions names are changed, but the concept is still there.

This kind of depends on the situation in my opinion, sometimes i like to break things into different functions if they're different concepts for simplicity and testing purposes.

Triple Tech
Jul 28, 2006

So what, are you quitting to join Homo Explosion?
Maybe it was code in transition. Better than before, but still clearly not the best. Mini commits, mini gains...

biznatchio
Mar 31, 2001


Buglord

ZorbaTHut posted:

"But 1500 lines is just too long! How can you ever understand it?" drat sight better than I can understand thirty 50-line functions strewn about my codebase.

Is strewing the functions about your codebase a requirement for breaking a monolithic function down into smaller bite-sized functions? I mean, I've always kept related functions right next to each other as much as possible, maybe I was just doing it wrong though.

Also I'm rather curious what this magical 1500-line algorithm that couldn't be subdivided further actually was.

dancavallaro
Sep 10, 2006
My title sucks

biznatchio posted:

Is strewing the functions about your codebase a requirement for breaking a monolithic function down into smaller bite-sized functions? I mean, I've always kept related functions right next to each other as much as possible, maybe I was just doing it wrong though.

Also I'm rather curious what this magical 1500-line algorithm that couldn't be subdivided further actually was.

As far as I know ZorbaTHut works at Google, so the magical algorithm was probably something like:

code:
void takeOverTheInternet() {
   // 1500 lines of pure loving magic
}

tef
May 30, 2004

-> some l-system crap ->
Badly remembered kernighan quote:

I have found the amount of local variables, and not the function length, to be a good indicator of if a function should be split up.

Janitor Prime
Jan 22, 2004

PC LOAD LETTER

What da fuck does that mean

Fun Shoe

biznatchio posted:

Also I'm rather curious what this magical 1500-line algorithm that couldn't be subdivided further actually was.
He works on Quest Helper a popular addon for World of Warcraft. It was originally some code that he inherited but basically had to solve the traveling salesman problem in real time using all the information that wow gives you access to. I remember opening that lua file once and seeing that function.

In his latest 1.0 branch he used a newer algorithm, but has to deal with more information. I haven't looked over the new code to see if it has slimmed down.

haveblue
Aug 15, 2005
Probation
Can't post for 6 hours!
Toilet Rascal

ZorbaTHut posted:

a struct containing 20 members that were only related by virtue of being the entire state of the algorithm.

I don't see what's wrong with this, combined with a reasonable set of manipulation/iteration functions.

Lurchington
Jan 2, 2003

Forums Dragoon

dancavallaro posted:

As far as I know ZorbaTHut works at Google, so the magical algorithm was probably something like:

code:
void takeOverTheInternet() {
   // 1500 lines of pure loving magic
}

The nerd patrol knows him as the author of QuestHelper.

I wouldn't be surprised at how many lines it'd take to do route-finding in Lua.

edit: I had the browser open for 2 hours and hadn't yet replied :(

Dijkstracula
Mar 18, 2003

You can't spell 'vector field' without me, Professor!

I admit I can't even imagine why an implementation of TSP (or, even any of its variants - bitonic TSP, euclidean TSP, etc) would take anywhere near 3000 lines :gonk:

1337JiveTurkey
Feb 17, 2005

Dijkstracula posted:

I admit I can't even imagine why an implementation of TSP (or, even any of its variants - bitonic TSP, euclidean TSP, etc) would take anywhere near 3000 lines :gonk:

ADD CURRENTEDGEWEIGHT TO TOTALEDGEWEIGHT GIVING TOTALEDGEWEIGHT,

baquerd
Jul 2, 2007

by FactsAreUseless

Dijkstracula posted:

I admit I can't even imagine why an implementation of TSP (or, even any of its variants - bitonic TSP, euclidean TSP, etc) would take anywhere near 3000 lines :gonk:

Genetic TSP?

Troglepus
Jan 10, 2007
A man for all and none.

Dijkstracula posted:

I admit I can't even imagine why an implementation of TSP (or, even any of its variants - bitonic TSP, euclidean TSP, etc) would take anywhere near 3000 lines :gonk:
Probably because it needs to be "solved" in real time, it will need a variety of heuristics/approximations to make it possible.

Dessert Rose
May 17, 2004

awoken in control of a lucid deep dream...

quadreb posted:

Genetic TSP?

From what he's said in the QuestHelper thread, that would seem to be the case (he talks about how it tends to get "stuck" on somewhat-good solutions when a large change would be better, which is exactly the issue a GA would face, for example)

GAs are still magic to me. I want to make a game with GA AIs.

blorpy
Jan 5, 2005

Ryouga Inverse posted:

From what he's said in the QuestHelper thread, that would seem to be the case (he talks about how it tends to get "stuck" on somewhat-good solutions when a large change would be better, which is exactly the issue a GA would face, for example)

GAs are still magic to me. I want to make a game with GA AIs.

Check out an existing GA lib like NEAT (or its modified cousin rtNEAT)

Mill Town
Apr 17, 2006

Ryouga Inverse posted:

From what he's said in the QuestHelper thread, that would seem to be the case (he talks about how it tends to get "stuck" on somewhat-good solutions when a large change would be better, which is exactly the issue a GA would face, for example)

GAs are still magic to me. I want to make a game with GA AIs.

If he's getting stuck in local maxima, maybe he should try something with a tuneable random component like simulated annealing.

Janitor Prime
Jan 22, 2004

PC LOAD LETTER

What da fuck does that mean

Fun Shoe

Mill Town posted:

If he's getting stuck in local maxima, maybe he should try something with a tuneable random component like simulated annealing.
I like simulated annealing, but it can still get stuck in a local maximum if the temperature is too low. Yeah it can reset itself, but if you don't use something else to help it along it might get stuck in that same local maximum again requiring another reset.

But that is besides the point, he's currently using Ant colony optimization. I don't think even the original quest helper used a genetic algorithm, but I can't recall what specific algorithm it used.

a slime
Apr 11, 2005

I would really like to see an implementation ant colony optimization that is 3000 lines. In this thread.

Zombywuf
Mar 29, 2008

Is the problem really TSP? Does WoW not have a geometry that obeys the triangle inequality?

baquerd
Jul 2, 2007

by FactsAreUseless

Zombywuf posted:

Is the problem really TSP? Does WoW not have a geometry that obeys the triangle inequality?

So you're claiming that in any geometry that obeys the triangle inequality (e.g. Euclidian), the fastest path visiting all of an arbitrary number of points can be determined without making it into a TSP?

Nigglypuff
Nov 9, 2006


BUY ME BONESTORM
OR
GO TO HELL

Zombywuf posted:

Is the problem really TSP? Does WoW not have a geometry that obeys the triangle inequality?

:allears: × slowly growing

Zombywuf
Mar 29, 2008

quadreb posted:

So you're claiming that in any geometry that obeys the triangle inequality (e.g. Euclidian), the fastest path visiting all of an arbitrary number of points can be determined without making it into a TSP?
Urgh I was assuming there was no need to actually find an optimal path, then looked at what QuestHelper actually does.

Seriously people, stop playing WoW and just play ProgressQuest while smoking crack. It's cheaper and better for you.

Adbot
ADBOT LOVES YOU

Nigglypuff
Nov 9, 2006


BUY ME BONESTORM
OR
GO TO HELL
For any geometry that obeys the triangle inequality, the fastest path visiting all of an arbitrary number of points can be determined without reducing the problem to that of the Travelling Salesman. I have discovered a truly marvellous proof of this, which this margin is too narrow to contain

  • 1
  • 2
  • 3
  • 4
  • 5
  • Post
  • Reply