▲ | pizza-wizard 7 months ago | |||||||||||||||||||||||||||||||
As someone without a lot of experience (in my first dev job now), would you care to expand on this? Does this mean that you wouldn’t have a function fn() that manipulates a global variable VAR, but rather you’d pass VAR like fn(VAR)? | ||||||||||||||||||||||||||||||||
▲ | WalterBright 7 months ago | parent | next [-] | |||||||||||||||||||||||||||||||
To expand on the other reply, some related things: 1. don't do console I/O in leaf functions. Instead, pass a parameter that's a "sink" for output, and let the caller decide what do with it. This helps a lot when converting a command line program to a gui program. It also makes it practical to unit test the function 2. don't allocate storage in a leaf function if the result is to be returned. Try to have storage allocated and free'd in the same function. It's a lot easier to keep track of it that way. Another use of sinks, output ranges, etc. 3. separate functions that do a read-only gathering of data, from functions that mutate the data Give these a try. I bet you'll like the results! | ||||||||||||||||||||||||||||||||
| ||||||||||||||||||||||||||||||||
▲ | maxbond 7 months ago | parent | prev | next [-] | |||||||||||||||||||||||||||||||
You've got the gist of it. By decoupling your function from the state of your application, you can test that function in isolation. For instance, you might be tempted to write a function that opens an HTTP connection, performs an API call, parses the result, and returns it. But you'll have a really hard time testing that function. If you decompose it into several tiny functions (one that opens a connection, one that accepts an open connection and performs the call, and one that parses the result), you'll have a much easier time testing it. (This clicked for me when I wrote code as I've described, wrote tests for it, and later found several bugs. I realized my tests did nothing and failed to catch my bugs, because the code I'd written was impossible to test. In general, side effects and global state are the enemies of testability.) You end up with functions that take a lot of arguments (10+), which can feel wrong at first, but it's worth it, and IDEs help enormously. This pattern is called dependency injection. https://en.wikipedia.org/wiki/Dependency_injection See also, the "functional core, imperative shell" pattern. | ||||||||||||||||||||||||||||||||
▲ | pjc50 7 months ago | parent | prev [-] | |||||||||||||||||||||||||||||||
Yes. Global variables or singletons are deeply miserable when it comes to testing, because you have to explicitly reset them between tests and they cause problems if you multithread your tests. A global variable is a hidden extra parameter to every function that uses it. It's much easier if the set of things you have to care about is just those in the declared parameters, not the hidden globals. |