Wednesday, 24 August 2011

Common Java method names

Following on from my discussion on some common class name prefixes and suffixes I wanted to discuss some method name conventions. Again, this all relates to Java - other languages are entrely different.

Common method name patterns

For better or worse, Java is a pattern-based language. By that, I mean that a lot of what is considered to be best practice in Java works is by following standard patterns. I don't just mean the Gang of Four book here. I mean the myriad of other patterns. These are often "visual patterns" as much as code patterns. For exmaple, by laying our code out in a certain consistent way, we aid the developer that follows us.

To be clear, I'm not saying that a pattern-based/naming approach is better than new language features. Its just that in Java that is the best we have to deal with many issues. (Cut and Paste is a way of life in Java, and its not always bad.)

One of these patterns is method names. This approach in Java is heavily influenced by two things - the size and dominance of the naming choices in the JDK, and the design of Java-Beans. Back in 2006, I proposed some additional variants for immutable classes, popularized by Joda-Time. This list includes both, plus some new ones I've added from ThreeTen/JSR-310. It isn't intended to list every possible method name convention, just some of the more popular, and those with specific semantics.

get Gets something from an object, such as bean.getBar(). This is also used with a key to lookup a list by index or a map by key, such as list.get(index) or map.get(key).
isChecks if something is true or false about an object or a property of an object. Example foo.isValid().
checkChecks if something is true, throwing an exception if it is not true. Example foo.checkValid().
containsChecks if a collection contains the queried object, such as coll.contains(bar). This can be used on classes that wrap, or otherwise act as, a collection.
removeRemoves an element from a collection, such as coll.remove(bar). This can be used on classes that wrap, or otherwise act as, a collection.
clearClears the object, typically but not necessarily a collection, so that it is "empty".
putMutable putter. This mutates the target object replacing or adding some form of key-value pair. Examples are map.put(key,bar) and bean.putFoo(key,bar)
setMutable setter. This mutates the target object setting a property, such as bean.getBar(bar).
withImmutable "setter". Returns a copy of the original with one or more properties changed, such as result = original.withBar(bar).
toConverts this object to an independent object, generally of another type. This generally takes no arguments, but might if it is appropriate.
asConverts this object to another object where changes to the original are exposed in the result, such as Arrays.asList().
buildBuilds another object based on either the specified arguments, the state of the target object, or both.
add/subtractAdds/subracts a value to the quantity. This mutates the target quantity (a number, date, time, distance...) adding/subracting the "foo" property. This name is also separately used for adding elements to a collection.
plus/minusImmutable version of add/subract for a quantity. Returns a copy of the original with the value added/subtracted. This name does not seem to work as well for adding elements to an immutable collection.
appendSometimes used to by methods that add to the end of a list, such as in StringBuilder.
resetResets the object back to a suitable initial state ready to be re-used.
past tenseUsed on immutable classes to helpfully suggest to the caller that the method doesn't mutate the target, but must instead be assigned to another variable. Returns a copy of the target object with the method name applied. Examples are immutable.normalized(), immutable.multipliedBy(bar), immutable.dividedBy(bar) and immutable.negated()

And here are some static method names:

of Static factory method. Typically used with immutable classes where constructors are private (permitting caching). This is used by EnumSet and ThreeTen/JSR-310.
valueOf Longer form of of used by the JDK.
from Longer form of of. JSR-310 uses 'from' when performing a "loose" conversion between types, ie. one that has a reasonable chance of failure. By contrast 'of' is used when the conversion is almost certain to succeed.
parse Static factory method that creates an instance of the class by parsing a string. This could just be another of method, but I think the semantics are clearer with a dedicated name.

Are there any more common names that I've missed? Comments welcome...

18 comments:

  1. "with" is often used as a mutable setter for a fluid style, especially for builders:

    Foo foo = new FooBuilder().withWidth(50).withHeight(100).build();

    It doesn't return a new version, it returns "this". I've also seen it for non-builders, as a more convenient form of "set".

    Another common pattern is add/remove (e.g., listeners).

    ReplyDelete
  2. Lawrence, I'm surprise at that usage of "with". I would recommend:

    Foo foo = new FooBuilder().width(50).height(100).build();

    You're right about add/remove listener, although thats a mini-pattern in its own right.

    ReplyDelete
  3. There are perhaps two categories of method naming patterns here; might serve you to separate them out.

    1) Convention-based. Method names that are too flexible to be defined/consolidated in an interface, but instantly recognizable to a coder and possibly discoverable via reflection. Examples: get/set, etc.

    2) API mimicry. Cases in which one could likely define a common interface (if in control of all involved classes). Mostly these consist of replicating well-known APIs (perhaps partially), allowing the original API to set the expectation for the result. For example, the collection methods add/remove/contains/clear, List's get, or Number's valueOf static method.

    The key distinction is that API mimicry may be more of a signal of unfavorable interface declarations than something that is irreconcilably a "pattern" in the sense that they cannot be sufficiently generalized to allow compiler checking. If the mimicked API changes, a case of mimicry could be retrofitted into implementation. For instance an implementation may not want the whole stream/file API, but once Closeable was introduced, a "close" method could become more broadly signalable.

    Many of the mimicking method names suggested are from Java APIs (Collections, Map, BigInt, etc.). Given a friendlier division of work - for instance JScience's definitions of GroupMultiplicative & GroupAdditive rather than the whole BigInt API - a mimicker may be able to leap from pattern to implementation.

    Mimicking an API is sometimes indicative of a mistake; the new implementation should consider implementing the "closest" interface where possible. That said, sometimes you need something that's not defined in an interface or there's no interface that fits the desired narrowness of a spec; in those circumstances, mimicry is the only option available in Java.

    --
    Apologies if I've wandered completely off topic.

    ReplyDelete
  4. Very interesting! My PhD thesis explores the meaning of method names in Java with respect to implementation semantics (http://www.duo.uio.no/sok/work.html?WORKID=112975&lang=en). You can browse automatically generated characterizations of method name patterns at http://phrasebook.nr.no.

    Here's the description of the 'find-[type]' pattern, for instance:

    "These methods very often contain loops, use local variables, have branches and have multiple return points, and often throw exceptions, do runtime type-checking or casting and are static, and comparatively often call themselves recursively. They rarely return void or write parameter values to fields."

    ReplyDelete
  5. James, once upon a time, I started an Apache Commons project that intended to create a lot of small interfaces with detailed semantics. I abandoned it when I realised that unless the JDK implemented the interfaces, they weren't going to be successful. Java as a language doesn't have the tools to make that interface driven approach sucessful outside without buyin from the JDK.

    Einarwh, interesting work in that thesis, and a good resource. The tool seems to be good at finding the pattern and basic method style, but still needs a human to add "real meaning".

    ReplyDelete
  6. Yup, JDK buy-in would be a must to always have on hand the interface you need (particularly when it's setting most expectations). But JDK buy-in doesn't seem likely because of the explosion in the number of interfaces that would produce.

    [If my sketchy understanding of Scala is correct...]
    Scala's structural typing half-addresses the problem. It lifts the API-mimicking name pattern into something that can occasionally pass for implementation (partially mimicking objects can use pre-existing code). However, it doesn't seem to enforce intent on the level that interfaces are meant to. Perhaps intent could be signaled with an annotation?

    ... For that matter, method annotation could clarify intent in Java as well. Something similar to javadoc's seealso:

    @SeeAlso/Referencing/Ala(Collection.class)
    remove(Object)

    If you really, really had to, those annotations could even be used to implement a labor-intensive intent-driven structural typing within Java.

    Perhaps, though, that idea is something the Scala community had already considered and discarded as useless... or something they already provide.

    ReplyDelete
  7. It's not Java, but Nokia discusses the naming patterns used in Qt here:
    http://developer.qt.nokia.com/wiki/API_Design_Principles

    ReplyDelete
  8. Have you seen "The Java Programmer's Phrase Book"?

    http://publications.nr.no/sle2008_postproceedings.pdf

    It contains some good empirical analysis of java method names.

    ReplyDelete
  9. It's a good idea in general to have consistent naming. It lowers the burden of re-learning in cases where you don't follow the patterns you usually follow.
    I especially like your clear distinction between pure (not state changing) and impure methods.
    See Thomas Mullen's paper for more on the subject of code readability:

    http://homepage.ntlworld.com/b.mullen4/paper/onward004-mullen.pdf

    ReplyDelete
  10. I believe you mean "past participle" instead of "past tense." :)

    ReplyDelete
  11. Useful reference blog entry. Thanks.

    How about adding "append" to the list?

    I think append implies mutability, so I came here looking for an alternative for immutable objects. Any suggestions?

    ReplyDelete
    Replies
    1. I agree that "append" should be in the list. The list would suggest the past participle "appendedWith", but that is a bit clunky. In JSR-310 we have methods prefixed by "at" - date.atTime(...). This makes sense in the time doamin. Is there anything that makes sense in your domain?

      Delete
    2. In my case, the name I was actually looking for was "resolve", led by the precedent in the java.nio.file and java.net.URI APIs.

      Delete
  12. Do you have any recommendation when to use "of" and when to use "from"? Java 8 DateTime mixes them as well (even in the same class), which makes me think, there must be a difference: from(TemporalAccessor), why isn't it of()? The JavaDoc of all the of() methods even say "obtains from".

    There's also alot of valueOf(String) in the JDK, but on the other hand also UUID.fromString(String).

    *confused*

    ReplyDelete
    Replies
    1. JSR-310 uses "from" when performing a "loose" conversion between types, ie. one that has a reasonable chance of failure. By contrast "of" is used when the conversion is almost certain to succeed.

      For example,

      - LocalDate.of(year, month, day) is bascially taking the three things that a LocalDate is _formed_ _of_.

      - LocalDate.ofYearDay(year, dayOfYear) is being explicit about the input arguments, and performing a very simple conversion that, providing the values are in range, will succeed.

      - LocalDate.from(temporal) is a slower and more complicated attempt to extract meaning _from_ the input temporal. The temporal could be a ZonedDateTime, in which case it succeeds, or it could be a YearMonth, in which case it fails. ie. the type system isn't helping you with "fr

      Delete
    2. Thanks! So the parse method could better be a "from" (instead "of", as you wrote), because it's a loose conversion between String and Type and it's (more) likely to fail?

      As an example, if java.net.URL followed this naming convention, every constructor would be "of", except the String ("spec") constructors, which would better be "from" or "parse"?

      Delete
    3. UUID: Yes, I'd use "parse", not "fromString". "parse" is a special kind of "from" method.

      java.net.URL: Basically the ones that take parts including "protocol" match the "of" concept. The ones that take "spec" may be "of", or may be "parse". I think there tend to be relatively few cases to use "from".

      Delete
    4. The "spec" constructors may be "of"? I felt like they would be "from" or "parse" (from what you said in your UUID explanation).

      Well, thanks for your thoughts, I think in the end it's just nitpicking and personal preference...

      Delete

Please be aware that by commenting you provide consent to associate your selected profile with your comment. Long comments or those with excessive links may be deleted by Blogger (not me!). All spam will be deleted.