Guía de estilo y convenciones de programacón para proyectos en Swift.
Esta guía es un intento para promover patrones que cumplan con las siguientes metas (no necesariamente en este orden):
- Conseguir un código más robusto y reducir la probabilidad de error al programar
- Aumentar la legibilidad del código, consiguiendo que sea más claro y autoexplicativo
- Reducir código innecesario y redundante
- Reducir los debates sobre estética
Si tienes sugerencias, por favor primero lee la guía para colaborar, después haz un pull request. ⚡
- Tabuladores, no espacios.
- El final del archivo acaba con una nueva línea.
- Utiliza sin miedo los saltos de línea para separar el código en trozos lógicos entendibles.
- No dejes espacios en blanco a la derecha de la línea. * No indentar las líneas en blanco. Éstas no deben llevar ni espacios ni tabulaciones.
Usar let foo = …
sobre var foo = …
siempre que sea posible (y también ante la duda). Solo usar var
si estás forzado a ello (es decir, sabes que el valor va a cambiar, por ejemplo cuando usas la propiedad weak
).
Razonamiento: La intención y el significado de ambas palabras reservadas está claro, pero utilizar let por defecto es más seguro y más limpio.
El let
-binding garantiza y recalca al programador que su valor nunca cambiará. Esto nos permite mantener una fuerte suposición que su valor no cambiará en el código que le sigue.
Se vuelve más fácil para razonar sobre el código. Al utilizar var
creamos duda si el valor de la variable cambiará, lo que deberemos comprobar de forma manual.
En cnosecuencia, siempre que veas un identificador var
, asume que su valor cambiará y pregúntate el porqué.
Cuando tengas que comprobar ciertas condiciones para continuar la ejecución, intenta salir cuanto antes. Entonces, en vez de:
if n.isNumber {
// Use n here
} else {
return
}
utiliza esto:
guard n.isNumber else {
return
}
// Use n here
También lo puedes hacer con if
, pero es mejor utilizar guard
porque guard
nos obliga a utilizar return
, break
o continue
, o el compilador lanzará un error. Por esta razón la salida está garantizada con guard
.
Si tienes un identificador foo
de tipo FooType?
o FooType!
, si es posible no fuerces su unwrap para conseguir el valor (foo!
).
En vez de forzar es preferible:
if let foo = foo {
// Use unwrapped `foo` value in here
} else {
// If appropriate, handle the case where the optional is nil
}
Como alternativa, en el caso de que quieras utilizar la concatenación de Optionals, puedes hacerlo así:
// Call the function if `foo` is not nil. If `foo` is nil, ignore we ever tried to make the call
foo?.callSomethingIfFooIsNotNil()
Razonamiento: Es más seguro el uso de if let
-binding para el resultado de optionals. El unwrapping forzado es más propenso a provocar errores en tiempo de ejecución.
Donde sea posible, utiliza let foo: FooType?
en vez de let foo: FooType!
ya que foo
puede ser nil (Por lo general, ?
se puede utilizar en vez de !
).
Razonamiento: Opcionales explícitos generan código más seguro. Implícitamente, el unwrapped de los optionals pueden producir fallos en tiempo de ejcución.
Cuando sea posible, omite el get
en las propiedades de solo lectura y en los subscripts de solo lectura.
Por lo tanto, escribe esto:
var myGreatProperty: Int {
return 4
}
subscript(index: Int) -> T {
return objects[index]
}
… y no esto:
var myGreatProperty: Int {
get {
return 4
}
}
subscript(index: Int) -> T {
get {
return objects[index]
}
}
Razonamiento: La intención y el significado de la primera versión es clara y ocupa menos líneas de código.
Funciones de alto nivel, tipos y variables deberían especificar siempre el control de acceso explícitamente:
public var whoopsGlobalState: Int
internal struct TheFez {}
private func doTheThings(things: [Thing]) {}
Sin embargo, las definiciones dentro de dichos objetos pueden expresar el control de acceso de manera implícita, en tal caso:
internal struct TheFez {
var owner: Person = Joshaber()
}
Razonamiento: Raravez es apropiado que las definiciones de alto nivel sean específicamente internal
. Al ser explicitos asegura que tendremos en mente esa decisión. Dentro de una definición, no es necesario volver a especificar el acceso de control otra vez, sería duplicar código y el acceso por defecto normalmente es el más razobnable.
Cuando especifiques el tipo de un identificador, coloca los dos puntos inmediatamente después del identificador, sin espacio entre ellos y a continuación escribe un espacio y el nombre del tipo.
class SmallBatchSustainableFairtrade: Coffee { ... }
let timeToCoffee: NSTimeInterval = 2
func makeCoffee(type: CoffeeType) -> Coffee { ... }
Razonamiento: El tipo especifica algo sobre el identificador, entonces los dos puntos deben ir con el identificador.
También, cuando especifiques el tipo de un diccionario, siempre pon los dos puntos inmediatamente después de la clave, seguido de un espacio y el valor.
let capitals: [Country: City] = [ Sweden: Stockholm ]
Por defecto, no escribas self
cuando accedas a propiedades o métodos de tu propia clase. Ya se entiende de forma implicita.
private class History {
var events: [Event]
func rewrite() {
events = []
}
}
Solamente incluye self
de forma explícita cuando sea requerido por Swift -por ejemplo dentro de un closure- o cuando haya conflicto entre los nombres de los parámetros.
extension History {
init(events: [Event]) {
self.events = events
}
var whenVictorious: () -> () {
return {
self.rewrite()
}
}
}
Razonamiento: Esto nos permite destacar self
dentro de closures y previene verbosidad en el resto del código.
Utiliza siempre struct, a menos que necesites una funcionalidad que solo se puede conseguir utilizando una clase (como identidad o desinicializadores).
La herencia por sí misma, normalmente no es razón suficiente para utilizar clases, ya que el polimorfismo puede ser ofrecido por los protocolos y el reciclado de código puede ser alcanzado mediante la composición.
Por ejemplo esta herencia de clase:
class Vehicle {
let numberOfWheels: Int
init(numberOfWheels: Int) {
self.numberOfWheels = numberOfWheels
}
func maximumTotalTirePressure(pressurePerWheel: Float) -> Float {
return pressurePerWheel * Float(numberOfWheels)
}
}
class Bicycle: Vehicle {
init() {
super.init(numberOfWheels: 2)
}
}
class Car: Vehicle {
init() {
super.init(numberOfWheels: 4)
}
}
podría ser refactorizada en estas definiciones:
protocol Vehicle {
var numberOfWheels: Int { get }
}
func maximumTotalTirePressure(vehicle: Vehicle, pressurePerWheel: Float) -> Float {
return pressurePerWheel * Float(vehicle.numberOfWheels)
}
struct Bicycle: Vehicle {
let numberOfWheels = 2
}
struct Car: Vehicle {
let numberOfWheels = 4
}
Razonamiento: Los tipo valor son más simples, más fáciles de razonar y su comportamiento es el esperado al declararlos con let
Las clases deberían empezar con final
y sólo ser cambiadas para permitir subclases si hemos identificado una necesidad válida y suficiente para herencia. Incluso en el caso de que haya muchas definiciones, la clase también debería ser final
, siguiendo las mismas reglas.
Razonamiento: Composición normalmente es preferible a herencia y, si optas por la herencia, que sea porque has lo has pensado cuidadosamente.
Los métodos de tipos parametrizados pueden omitir el tipo de los parámetros al recibir el tipo cuando son identicos a los del receptor. Por ejemplo:
struct Composite<T> {
…
func compose(other: Composite<T>) -> Composite<T> {
return Composite<T>(self, other)
}
}
podría ser sustituido por:
struct Composite<T> {
…
func compose(other: Composite) -> Composite {
return Composite(self, other)
}
}
Razonamiento: Omitiendo el tipo de los parámetros redundantes deja más clara la intención y por el contrario, cuando especificamos el tipo, hace que sea evidiente que este es otro diferente.
Cuando definas operadores, escribe espacios en blanco antes y después. En vez de:
func <|(lhs: Int, rhs: Int) -> Int
func <|<<A>(lhs: A, rhs: A) -> A
escribe:
func <| (lhs: Int, rhs: Int) -> Int
func <|< <A>(lhs: A, rhs: A) -> A
Razonamiento: Los operadores son caracteres de puntuación, los cuales pueden ser difíciles de leer si están pegados a un tipo o parámetro. Añadiendo estos espacios separamos los operadores de una forma clara.