Scala: Where should I place import statements?
Scala allows to import almost whatever you want, wherever you want, and this is great. But are there any considerations I should take into account when importing something inside class, method or just any block? How does it relate to performance, style, maintainability of code, etc?
Generally I try to obey these rules (made up by myself):
- If I'm importing something external from other package I always p开发者_如何学Clace it at the top just after the "package".
- If I'm using something more than once in the same file, I also import it at the top.
- Otherwise I place my imports at the top of the relevant class/trait/object.
- I avoid to import things in methods and blocks.
- I try to avoid importing contents of instance objects, unless I have a really good reason to do so.
- I would avoid renaming and "hiding" unless to resolve name collisions, but I have never needed that yet.
Do those "rules" make sense to you? Am I restricting myself too much?
It usually makes sense to restrict the scope of something (e.g. a variable or a method) to the "least" as it is possible. For example, use an inner def as opposed to one at the class level. Why should import statements be any different? Why pollute a class with imports which are only used in a single block?
I like to declare imports as close to where they are used as possible!
The upshot of this is that common utilities, both libraries like scalaz and my own, tend to get imported once at the top-level (because they are used throughout the class). Whereas stuff like I/O gets imported locally, only where it is used
Put imports at the top of a file.
Having them scattered all over a file makes it harder to read the code:
- They convey no logical meaning, they're merely an alias; thus, they "pollute" the code which reflects the logical aspect of the program.
- One cannot expect where to find them (this is what conventions are for).
- In files with multiple references to entities with the same name but a different namespace, it's hard to keep track of what that name references in every scope.
I see no benefit in placing them next to their closest scope. Actually, with this reasoning in mind, one should never use them at all; instead, one should always use the full namespace for every referenced entity. Does that make any sense? IMHO, no.
You should remember that Scala is still compiled language, and all the rules applicable to Java as a compile language are applicable to a Scala also. Compiler should know what symbol you mean when you say List
or Function
.
You could use in-block import statements but use them carefully. Overusing may lead to inconsistent understanding of the source files by other people. It would be inconvenient if single class boundary will use two different definition of List
dependent on context.
The import guidelines in Effective Scala say:
Put imports at the top of the file
The reader can refer to all imports in one place.
In some code bases I've worked with, there was a tendency to import the same thing multiple times in several places in the same file, because the developer didn't check for this. It would be nice if IDEs could automatically promote block-level imports if they detect multiple instances of them, but AFAIK the one I use (IntelliJ) doesn't do this.
When working on large files, many people tend to only look at their local piece of code, so they may locally import something that conflicts with other parts of the package (e.g. in one class Promise
is imported from scala.concurrent
, and in another from akka.dispatch
), and it's harder to detect this when you have imports sprinkled all over the place.
This may or may not be relevant to your particular code base, but I personally tend to be pessimistic about the code I maintain (entropy and all that) and thus try to institute rules from the start that would ensure maintainability in the long run.
Edit: removed a reference to ScalaStyle's BlockImportChecker rule, which is not related.
No.
I avoid to import things in methods and blocks.
Why?
If you feel a difference, there has to be an objective difference, because feelings are objective events and don't origin from nothing.
They can be measured - not easily and precisely with our current instruments - and maybe you aren't able to draw connections to other objective facts, which explain these feelings.
To me it looks as if you happen to stick to a kind of order rule, "imports belong to the top", which might origin from a language you learned before, where there is an objective necessity to place them on top. Without the necessity, you might order them close to the first dependency, to delete it easily, if you delete the depending code and make it easy for the reader, to figure out, why the import is there, and from where a class or trait is imported, for example collections.mutable might be such an import.
精彩评论