Skip to content

Instantly share code, notes, and snippets.

@erica
Created Feb 4, 2016
Embed
What would you like to do?

##Introduction because I have OCD

Swift prizes clarity. Its parameter labeling system emphasizes self-documentation and guides code production. In nearly every case, labels follow three simple rules:

  • Skip argument labels for a method or function's first parameter
  • Use argument labels for a method or function's subsequent parameters
  • Require argument labels for initializers

These base rules enhance Swift legibility. Unlike other languages whose positional argument names have meaning only within the implementation context, Swift's labels convey use and meaning at the calling site. This creates better communication, enhances maintainability, and adheres to the principle that code is written rarely and read and reviewed often.

At times, special circumstances may apply to your code as explored in the following rules:


###Dave: If and only if the first argument could complete a sentence* beginning in the base name and describing the primary semantics of the call, it gets no argument label:

a.contains(b)  // b completes the phrase "a contains b"
a.mergeWith(b) // b completes the phrase "merge with b"

a.dismiss(animated: b) // "a, dismiss b" is a sentence but 
                       // doesn't describe the semantics at all, 
                       // thus we add a label for b.

a.moveTo(x: 300, y: 400) // "a, move to 300" is a sentence 
                         // but doesn't describe the primary 
                         // semantics, which are to move in both
                         // x and y.  Thus, x gets a label.

a.readFrom(u, ofType: b) // "a, read from u" describes
                         // the primary semantics, so u gets no
                         // label. b is an
                         // option that tunes the primary
                         // semantics

[Note that this covers all the direct object cases and, I believe, all the default argument cases too, so maybe that exception can be dropped. We still need the exceptions for full-width type conversions and indistinguishable peers]


  • Skip first argument labels when the first argument completes a sentence established in the base name. If the argument describes a call's primary semantics, it does not require a label:
    a.contains(b)  // b completes the phrase "a contains b"
    a.mergeWith(b) // b completes the phrase "merge with b"
    a.readFrom(u, ofType: b) // "a, read from u" describes
                             // primary semantics so u gets no
                             // label. 
                             // b is an option that tunes the 
                             // primary semantics

Changes Rule is now a directive. Dismiss and moveTo are moved to their own rules. I only minorly tweaked the comments.


###Dave: Note: when there is a noun in the base name describing the role of the first argument, we skip it in considering this criterion:

 a.addObserver(b) // "a, add b" completes a sentence describing 
                  // the semantics.  "Observer" is omitted in 
                  // making this determination.

  • Skip the first argument label when a noun in the base name describes the first argument's role.
   a.addObserver(b) // "add b" completes a meaningful sentence that
                    // defines the intended semantics.  The first
                    // argument is the "Observer".

Changes Rephrasing, rule directive, rewrote comments


###Dave: 3. (this one is separable) When the first argument is the name or identifier of the subject in the base name, do not label it or describe it in the base name.

 a.transitionToScene(.GreatHall)               // yes
 a.transitionToSceneWithIdentifier(.GreatHall) // no

 let p = someFont.glyph("propellor")           // yes
 let p = someFont.glyphWithName("propellor")   // no
 let p = someFont.glyph(name: "propellor")     // no

  • Move the first argument label to the base name when it describes a name or identifier that acts as the subject of the base action.
     a.transitionToScene(.GreatHall)               // yes
     a.transitionToSceneWithIdentifier(.GreatHall) // no

     let p = someFont.glyph("propellor")           // yes
     let p = someFont.glyphWithName("propellor")   // no
     let p = someFont.glyph(name: "propellor")     // no

Changes Rule directive, grouped below with similar rule


###Dave:

  1. Words that describe attributes of an already-existing instance should go in the base name rather than in a label:

    a.tracksHavingMediaType("Wax Cylinder") // yes a.removeFirstTrackHavingMediaType("BetaMax") // yes

    a.tracks(mediaType: "Wax Cylinder") // no a.removeFirstTrack(havingMediaType: "BetaMax") // no

[yes, we could use "With" instead of "Having", but it's more ambiguous]

Words that describe attributes of an instance to be created should go in argument labels, rather than the base name (for parity with initializers):

 AudioTrack(mediaType: "BetaMax")                   // initializer
 trackFactory.newTrack(mediaType: "Wax Cylinder")   // yes

 trackFactory.newTrackWithMediaType("Wax Cylinder") // no

  • Move the first argument label to the base name when it describes argument attributes of existing instances.
     a.tracksOfMediaType("Wax Cylinder")      // yes
     a.removeFirstTrackOfMediaType("BetaMax") // yes

     a.tracks(mediaType: "Wax Cylinder")            // no
     a.removeFirstTrack(havingMediaType: "BetaMax") // no

Changes Rule directive, grouped with previous rule, split out initializer-like rules for later, changed first "Having" to "Of" because that's the way I roll.


  • Use first label arguments when the first parameter is semantically distinct from the base name and does not complete a meaningful "sentence"
    a.dismiss(animated: b) // "a, dismiss b" is a sentence but 
                           // doesn't describe the semantics at all, 
                           // thus we add a label for b.

Changes Broke this one out into its own rule because it's about when not to merge into base name but still use a label. By the way, I loathe "we" voice.


  • Use all argument labels when the relationship between arguments is semantically stronger than the relationship between the first argument and the base name.
    moveTo(x: a, y: b)
    login(userName: a, password: b)
    constructColor(red: r, green: g, blue: b, alpha: a)

Changes Completely new rule


  • Omit labels for argument peers that cannot be usefully distinguished.
    min(number1, number2)
    zip(sequence1, sequence2)

Changes Stolen from doc, moved here because it fits, and rephrased a teeeeeny bit to make it more a directive

What follows next is a group of initializer-ish rules grouped together.


  • Use explicit argument labels to describe attributes of an instance that's being created. Your calls should resemble initializers.
     AudioTrack(mediaType: "BetaMax")                   // initializer
     trackFactory.newTrack(mediaType: "Wax Cylinder")   // yes

     trackFactory.newTrackOfMediaType("Wax Cylinder")   // no
  • Use first argument labels that would have normally appeared in the base name when building groups of related calls whose implementations are distinguished specifically by their parameters. Your calls should resemble initializers.
  login(userName: a, password: b) // not loginWithUserName(a, password: b)
  login(credential: a) // not loginWithCredential(a)

Changes New rule


  • Skip first argument labels for initializers when using full width type conversions, that is when initializing from instances of another type.
 extension String { 
     // Convert `x` into its textual representation 
     // in the given radix
     init(_ x: BigInt, radix: Int = 10) 
 }
 text = "The value is: "
 text += String(veryLargeNumber)
 text += " and in hexadecimal, it's"
 text += String(veryLargeNumber, radix: 16)
  • Use first argument labels when narrowing initial values to make it conform to restrictions within the new type. The label should describe how the instance will be modified:
 extension UInt32 {
     init(_ value: Int16) // Widening, so no label 
     init(truncating bits: UInt64)
     init(saturating value: UInt64)
 }

Changes Both of these are stolen from docs, but rephrased to be more readable


Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment