Hungarian notation refers to an identifier-naming convention proposed by Charles Simonyi at XEROX PARC. It proposes that each identifier's name begin with a semantically-meaningful prefix indicating the pragmatic purpose of the variable in a given context. This is referred to as "app Hungarian", as opposed to "systems Hungarian" which tries to strictly encode a variable's programmatic type into the prefix — a practice which would not be as useful in Java, which already enforces types for its variables.
For notes describing the specific notations used in the zerobandwidth Android library, see Notational Conventions.
The One True Brace Style is "always on the next line". Anything else that you have seen or heard to the contrary was trying nefariously to mislead you into a life of depravity and sin.
Brackets around complicated expressions should have comfortable spacing within, while brackets around single identifiers may remain dense.
Semicolons should be padded by a space from the end of the line, except where it would go over the canonical length of a line, which is 80 characters.
All classes, members, and methods shall be documented with JavaDoc comments. A new version of the library's complete JavaDoc set is generated with each release.
Any class, member, or method which is added to the library shall be decorated
with a @since
annotation, showing that it is a part of the
zer0bandwidth-net/android
library, the version in which the item
was added to the library, and the GitHub issue number to which the related
commit was ascribed. This distinctive format, in addition to carrying
valuable versioning information, is chosen to be obviously distinctive from
the code provided by other zer0bandwidth.net
libraries and apps,
as well as the code produced by anyone who would consume this library.
Similarly, any item which is deprecated from the library shall be decorated
with a @deprecated
annotation. This annotation will show the
version of the library in which the item was first considered deprecated,
and, if applicable, will also suggest a usable alternative/replacement, and
describe the plans for removing the item from the library altogether.
Each class definition which writes anything to the ADB logs will include a
static LOG_TAG
constant which is used as the tag for Android
system logs generated by that class. The value of this constant is,
conventionally, the simple name of the class itself.
When writing to the ADB logs, if there is any dynamic data to be included in
the entry's text, then the text shall be composed with a
StringBuilder
. We use this class for the following reasons:
+
operator.
String.format()
method.
StringBuffer
.
The static portions of the log line should enclose the dynamic content with square-brackets, to make the value "pop" when reading the text of the entry in the ADB logs.
Any non-static method that does not otherwise return a value should return
the instance of the object that executed it, rather than being declared
void
. This allows consumers of the library class to invoke the
class's methods in a long, continuous chain, rather than repeating its
identifier multiple times.
public class Water { protected int m_nDrops = 0 ; protected Color m_color = Color.CLEAR ; public Water flowQuickly() { m_nDrops += 10 ; return this ; } public Water flowSlowly() { m_nDrops += 1 ; return this ; } public Water turnColor( Color color ) { m_color = color ; return this ; } public String toString() { return (new StringBuilder()) .append( "You have " ).append( m_nDrops ) .append( " drops of " ).append( m_color.toString() ) .append( " water." ) .toString() ; } } public class WaterTest { public void testWater() { String s = (new Water()) .flowQuickly() .flowSlowly() .turnColor( Color.GREEN ) .flowQuickly() .toString() ; assertEquals( "You have 21 drops of green water.", s ) ; } }
Each class should keep its member fields protected
unless it is
absolutely necessary to hide them at private
scope for some
specific reason. The protected
scope conceals the inner workings
of the class from its consumers, but allows classes in the same package to
use the items directly. Most significantly, this allows classes that execute
automated unit tests to access the class members directly when evaluating
test assertions, which makes the tests much more efficient, and easier to
write. It also follows the general convention of "fluidity" described in the
previous section.
While member fields should be concealed behind protected
scope,
the class should also provide adequate accessor and mutator methods for those
fields, where appropriate. Mutators for each field might or might not be
required; instead, it might be more appropriate for a mutator to ingest a
single object or set of inputs, from which the class members are extracted
and processed.
Conventionally, the accessors and mutators provided by this library are not
quite in the "bean" paradigm (whose mutators are declared void
)
but not quite the "builder" paradigm (in which mutators are fluid but in a
separate class). Instead, the mutator methods themselves are fluid but still
provided by the class itself; that is, each class is its own
"builder". This allows consumers of the class to interact with it more
easily.
public class Flargle { protected String m_sFoo ; protected String m_sBar ; public String getFoo() { return m_sFoo ; } public Flargle setFoo( String s ) { m_sFoo = s ; return this ; } public String getBar() { return m_sBar ; } public Flargle setBar( String s ) { m_sBar = s ; return this ; } public String toString() { return (new StringBuilder()) .append( m_sFoo ).append( " " ) .append( m_sBar ).append( "!" ) .toString() ; } } public class FlargleBarg { public void testFlargle() { Flargle flargle = (new Flargle()).setFoo( "Hello" ).setBar( "world" ) ; assertEquals( "Hello world!", flargle.toString() ) ; } }
As a general rule, if it's worth copying and pasting once, then it's worth pasting into a method which can be reused. This increased modularity ensures that, if that bit of repetitive code ever has to be tested or changed, then those tests and changes will be needed only once, and all consumers of that code can be left alone as much as possible; that is, the singular change may be confined to a nicely-limited context.
Besides, you never know when that new, factored-out method might become useful in some other way. Many of the features of this library were, themselves, bits of reusable code required across several of our own projects.
/** * Illustrates library coding standards. * @since zer0bandwidth-net/android 0.1.7 (#36) */ public class CodingExample extends CodingStandard implements Practice{ private static final boolean ALWAYS_NEXT_LINE = true ; /** * Lines longer than 80 characters should be broken reasonably, like at the * equals (=) sign for an assignment operation, or before one of the dots in * a chain of fluid method invocations. */ private static final String LOG_TAG = CodingExample.class.getSimpleName() ; /** Members get the 'm_' prefix. Signed integers get 'z'... */ protected int m_zFoo ; /** ...while unsigned integers get 'n'. */ protected int m_nAllGoodThings ; /** * Templatized items stay dense (no whitespace within brokets). * A collection of nondescript objects gets 'a' (array-like) and 'o' * (objects). */ protected List m_aoThings ; /** Trivial one-liner methods, like accessors, can indeed be one-liners. */ public int getFoo() { return m_zFoo ; } /** * Trivial mutators might be one line, too. * Mutators should also be "fluid" (return the mutated object for chaining). */ public CodingExample setFoo( int z ) { m_zFoo = z ; return this ; } /** * This function does stuff with things. * @param oThings an object containing things with which to do stuff * @return (fluid) */ public CodingExample doStuff( Things oThings ) { if( oThings.areGoodThings() ) { // Briefly explain a block's contents on its opening brace line. ++m_nAllGoodThings ; oThings.setCounted(true) ; } else // Briefly explain a one-liner's contents on its if or else line. oThings.setCounted(true) ; switch( oThings.getSomeNumberThing() ) { // The logic of this switch is silly, but illustrates spacing. case 0: m_zFoo = 0 ; break ; case 1: m_zFoo = 1 ; break ; default: m_zFoo += oThings.getSomeNumberThing() ; } // For type casts, keep terms dense but be paranoid with parens. CastedThing oCasted = ((CastedThing)(oThings.get(0))) ; // Things that look like array indices should be dense if simple. Thing[] aoThings = oThings.toArray() ; Thing oFourth = aoThings[4] ; Thing oFifth = oThings.get(5) ; // Use judgment when the index is NOT something simple. Thing oLast = aoThings[ aoThings.length - 1 ] ; // Complicated log lines should be constructed with StringBuilders, // since they are faster than string concatenation, but don't need to // survive long enough to be thread-safe like StringBuffers. Log.d( LOG_TAG, (new StringBuilder()) .append( "We have [" ) .append( m_aoThings.size() ) .append(( m_aoThings.size() == 1 ? "] thing." : "] things." )) .toString() ); return this ; } }