coding practices: include whole module or only the needed function

H. S. Teoh via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Mon Oct 6 16:17:43 PDT 2014


On Mon, Oct 06, 2014 at 09:24:54PM +0000, AsmMan via Digitalmars-d-learn wrote:
> Which practice do you use: if you need only one or two functions from
> a module:
> 
> import myModule : func, func2;
> 
> or (import whole module, assuming no function name conflits of course)
> 
> import myModule;
> 
> any words why one over the other are welcome.
> 
> I like the first one why it explicitly show why I'm importing such a
> module and I think (personally) it make code more easy to
> read/understand/maitain.

Agreed. Recently, I realized more and more that local imports are the
way to go if you want maintainable code, specifically, easily-
refactorable code.

My old C/C++ habit was to put all imports at the top of the file, but
the problem with that is, when you need to break a source file into
several smaller files, it's a pain to figure out which imports are used
by which pieces of code. It's either that, or copy all the imports
everywhere, which is wasteful when one of the new files doesn't actually
need most of the imports.

Explicitly naming imported symbols is also important, especially given
the recent bugs related to introducing unwanted symbol conflicts into a
scope. It also tells you exactly what symbols a particular piece of code
depends on; for example, mymodule.d may import std.algorithm, but it's
not clear exactly what in std.algorithm is actually used, and where.
Specifically importing std.algorithm : find in func1() and std.algorithm
: nextPermutation in the respective scopes that need it, makes the code
more maintainable -- readers can quickly find out where 'find' and
'nextPermutation' come from without needing to search the docs (or
memorize function names).

Then when you need to move func1() and func2() new files, you know that
newfile1.d only depends on std.algorithm.find, and newfile2.d only
depends on std.algorithm.nextPermutation. So next time you rewrite the
code in func1() and you realize that find() is no longer needed, you can
delete the import statement without worrying that it will break other
parts of the code.

Having said that, though, the *disadvantage* of using scoped explicit
imports is that if you use a lot of symbols from a particular module,
or use the same symbol in many scattered places, then your import
statements could become rather unwieldy and repetitive:

	module mymodule;

	void func1(...) {
		import std.algorithm : find, canFind, nextPermutation,
			remove, /* a huge long list here */;
		import std.range : isForwardRange;
		import std.array : front, empty, popFront;
		...
	}

	void func2(...) {
		import std.algorithm : canFind, remove,
			cartesianProduct, /* another huge long list
			here, repeating many items from previous list
			*/;
		import std.range : isInputRange;
		import std.array : front, empty, popFront;
		...
	}

So depending on circumstances, sometimes I'd just get lazy and just
use a generic import instead.

Of course, the example above is extreme... normally, you don't need to
use 20 functions from std.algorithm inside a single function; usually
just a small handful is enough. So the above situation should be rather
rare. I also find that in practice, it's my own modules (rather than
Phobos modules) that tend to have symbols that get used repeatedly; in
that case I just use a generic import for that. The one exception is
std.range, which tends to be needed everywhere if you write range-based
code a lot. Usually I just stick `import std.range` at the top of the
file in that case.


> Also it's very useful inside functions (local imports) where we can
> "overload" the function like in:
> 
> int f(int arg)
> {
>    import foo : f;
> 
>    return f(somethingElse, arg);
> }
> 
> I used it recently.

I'm not sure this is a good idea! It makes the code harder to
understand, and if it's a team project, you can bet that one day,
somebody will come and introduce a bug because he/she refactored the
code but failed to notice that the two 'f's refer to two different
things. I'd much rather use the renaming feature of imports:

	int f(int arg)
	{
		import foo : fooF = f;
		return fooF(somethingElse, arg);
	}


T

-- 
It only takes one twig to burn down a forest.


More information about the Digitalmars-d-learn mailing list