├── CHANGELOG.md ├── LICENSE └── README.md /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2020-10-25 2 | 3 | - Opciones funcionales: Se recomienda implementar en un `struct` la interfaz `Option` en lugar de capturar valores con un `closure`. 4 | 5 | # 2020-10-23 6 | 7 | - Evite la mutación de variables globales (nueva sección). 8 | 9 | # 2019-10-11 10 | 11 | - Sugerir contexto específico para mensajes de error. 12 | 13 | # 2019-10-10 14 | 15 | - Versión inicial. -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 47 | 48 | # Guía de estilo de Go en Uber 49 | 50 | Esta guía es la versión traducida al español de la versión original creada por [Uber](https://github.com/uber-go/guide). Intentaremos mantener esta versión actualizada casi al mismo tiempo que la original. Si queréis contribuir podéis dejar vuestros PRs, pero recordar que siempre tendrán que ser sobre correcciones sobre la traducción o añadir nuevos cambios que no hayamos actualizados. 51 | 52 | Además iremos dejando todos los cambios que se vayan realizando en el fichero [CHANGELOG.md](https://github.com/friendsofgo/uber-go-guide-es/blob/master/CHANGELOG.md) 53 | 54 | ## Índice 55 | 56 | - [Introducción](#introduccin) 57 | - [Directrices](#directrices) 58 | - [Punteros a interfaces](#punteros-a-interfaces) 59 | - [Receptores e interfaces](#receptores-e-interfaces) 60 | - [No es necesario inicializar los Mutexs](#no-es-necesario-inicializar-los-mutexs) 61 | - [Limitaciones copiando Slices y Maps](#limitaciones-copiando-slices-y-maps) 62 | - [Defer para limpiar](#defer-para-limpiar) 63 | - [El tamaño de los Canales es Uno o Ninguno](#el-tamao-de-los-canales-es-uno-o-ninguno) 64 | - [Empezando Enums en Uno](#empezando-enums-en-uno) 65 | - [Error Types](#error-types) 66 | - [Error Wrapping](#error-wrapping) 67 | - [Manejando los errores de Type Assertion](#manejando-los-errores-de-type-assertion) 68 | - [No Panic](#no-panic) 69 | - [Usa go.uber.org/atomic](#usa-gouberorgatomic) 70 | - [Evite globales mutables](#evite-globales-mutables) 71 | - [Rendimiento](#rendimiento) 72 | - [Usar strconv en lugar fmt](#usar-strconv-en-lugar-fmt) 73 | - [Evitar convertir strings a bytes](#evitar-convertir-strings-a-bytes) 74 | - [Especificar una capacidad aproximada al Map](#especificar-una-capacidad-aproximada-al-map) 75 | - [Estilo](#estilo) 76 | - [Se consistente](#se-consistente) 77 | - [Agrupar declaraciones similares](#agrupar-declaraciones-similares) 78 | - [Agrupaciones y orden en los imports](#agrupaciones-y-orden-en-los-imports) 79 | - [Nombre de paquetes](#nombre-de-paquetes) 80 | - [Nombres de funciones](#nombres-de-funciones) 81 | - [Alias en los imports](#alias-en-los-imports) 82 | - [Agrupación de funciones y orden](#agrupacin-de-funciones-y-orden) 83 | - [Reducir el código anidado](#reducir-el-cdigo-anidado) 84 | - [Else innecesario](#else-innecesario) 85 | - [Declaración de variables globales](#declaracin-de-variables-globales) 86 | - [Prefijo _ para las globales privadas](#prefijo-_-para-las-globales-privadas) 87 | - [Composición en Structs](#composicin-en-structs) 88 | - [Usa el nombre de los campos al inicializar Structs](#usa-el-nombre-de-los-campos-al-inicializar-structs) 89 | - [Declaración de variables locales](#declaracin-de-variables-globales) 90 | - [nil es un slice válido](#nil-es-un-slice-vlido) 91 | - [Reducir el scope de las variables](#reducir-el-scope-de-las-variables) 92 | - [Evitar parámetros planos](#evitar-parmetros-planos) 93 | - [Evita declarar strings con escapados](#evita-declarar-strings-con-escapados) 94 | - [Inicializando referencias a Struct](#inicializando-referencias-a-struct) 95 | - [Inicializando Maps](#inicializando-maps) 96 | - [Formatos de Strings fuera del Printf](#formatos-de-strings-fuera-del-printf) 97 | - [Nombra funciones al estilo Printf](#nombra-funciones-al-estilo-printf) 98 | - [Patrones](#patrones) 99 | - [Test Tables](#test-tables) 100 | - [Opciones funcionales](#opciones-funcionales) 101 | 102 | ## Introducción 103 | 104 | Los estilos son las convenciones que gobiernan nuestro código. El término estilo aquí es un poco inapropiado, ya que estas convenciones cubren algo más que el formato de nuestros ficheros, del cual ya se encarga sobradamente `gofmt`. 105 | 106 | La finalidad de esta guía es la de tratar de describir en detalle como se escribe código Go en Uber. Estas reglas han sido creadas para mantener un código manejable, haciendo que los desarrolladores puedan realizar nuevas tareas o funcionalidades de manera más productiva. 107 | 108 | Esta guía fue creada originalmente por [Prashant Varanasi] y [Simon Newton] como una forma de poner al día a algunos compañeros en Go. Con los años se ha ido mejorando con el feedback que ha ido recibiendo. 109 | 110 | [Prashant Varanasi]: https://github.com/prashantv 111 | [Simon Newton]: https://github.com/nomis52 112 | 113 | Este documento cubre las convenciones idiomáticas en Go que se siguen en Uber. Muchas de éstas son pautas generales para Go, mientras que otras se extienden a recursos externos: 114 | 115 | 1. [Effective Go](https://golang.org/doc/effective_go.html) 116 | 2. [The Go common mistakes guide](https://github.com/golang/go/wiki/CodeReviewComments) 117 | 118 | Todo el código debe estar libre de errores cuando se ejecute tanto `golint` como `go vet`. Recomendamos configurar tu editor para ejecutar: 119 | 120 | - `goimports` al guardar 121 | - `golint` y `go vet` para comprobar errores. 122 | 123 | Puedes encontrar más información, en la sección de soporte de editores aquí: 124 | 125 | ## Directrices 126 | 127 | ### Punteros a interfaces 128 | 129 | Casi nunca necesitarás un puntero a una interfaz. Deberías pasar las interfaces como valor, el dato subyacente puede seguir siendo un puntero. 130 | 131 | Una interfaz se compone de dos campos: 132 | 133 | 1. Un puntero a información específica del tipo. Puedes pensar en ello como un `type`. 134 | 2. Puntero a datos. Si el dato almacenado es un puntero, será almacenado directamente. Si el dato almacenado es un valor, entonces se almacenará un puntero a su valor. 135 | 136 | Si quieres que tu interfaz contenga métodos que puedan modificar los datos subyacentes, tendrás que utilizar un puntero. 137 | 138 | ### Receptores e interfaces 139 | 140 | Métodos que reciben un receptor por valor, pueden ser llamados tanto como punteros como por valor. 141 | 142 | Por ejemplo, 143 | 144 | ```go 145 | type S struct { 146 | data string 147 | } 148 | 149 | func (s S) Read() string { 150 | return s.data 151 | } 152 | 153 | func (s *S) Write(str string) { 154 | s.data = str 155 | } 156 | 157 | sVals := map[int]S{1: {"A"}} 158 | 159 | // Puedes llamar a Read usando un valor 160 | sVals[1].Read() 161 | 162 | // Producirá un error de compilación: 163 | // sVals[1].Write("test") 164 | 165 | sPtrs := map[int]*S{1: {"A"}} 166 | 167 | // Puedes llamar a ambos Read y Write usando un puntero 168 | sPtrs[1].Read() 169 | sPtrs[1].Write("test") 170 | ``` 171 | 172 | Del mismo modo, un puntero puede satisfacer una interfaz, incluso si el método tiene un receptor por valor. 173 | 174 | ```go 175 | type F interface { 176 | f() 177 | } 178 | 179 | type S1 struct{} 180 | 181 | func (s S1) f() {} 182 | 183 | type S2 struct{} 184 | 185 | func (s *S2) f() {} 186 | 187 | s1Val := S1{} 188 | s1Ptr := &S1{} 189 | s2Val := S2{} 190 | s2Ptr := &S2{} 191 | 192 | var i F 193 | i = s1Val 194 | i = s1Ptr 195 | i = s2Ptr 196 | 197 | // El siguiente código no compilará, ya que s2Val es un valor, y no hay método f() que esperen ser llamados por un valor. 198 | // i = s2Val 199 | ``` 200 | 201 | Effective Go tiene muy buena documentación sobre [Punteros vs. Valor]. 202 | 203 | [Punteros vs. Valor]: https://golang.org/doc/effective_go.html#pointers_vs_values 204 | 205 | ### No es necesario inicializar los Mutexs 206 | 207 | El valor por defecto de `sync.Mutex` y `sync.RWMutex` es suficiente, no necesitarás en la mayoría de los casos crear un puntero hacia un `mutex`. 208 | 209 | 210 | 211 | 212 | 227 |
IncorrectoCorrecto
213 | 214 | ```go 215 | mu := new(sync.Mutex) 216 | mu.Lock() 217 | ``` 218 | 219 | 220 | 221 | ```go 222 | var mu sync.Mutex 223 | mu.Lock() 224 | ``` 225 | 226 |
228 | 229 | Si usas un `struct` como puntero, entonces el `mutex` no necesita ser un puntero. 230 | 231 | Los `structs` privados que usen `mutex` para proteger campos del `struct` deberían estar compuestos por `mutex`. 232 | 233 | 234 | 235 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 |
236 | 237 | ```go 238 | type smap struct { 239 | sync.Mutex // sólo para tipos privados 240 | 241 | data map[string]string 242 | } 243 | 244 | func newSMap() *smap { 245 | return &smap{ 246 | data: make(map[string]string), 247 | } 248 | } 249 | 250 | func (m *smap) Get(k string) string { 251 | m.Lock() 252 | defer m.Unlock() 253 | 254 | return m.data[k] 255 | } 256 | ``` 257 | 258 | 259 | 260 | ```go 261 | type SMap struct { 262 | mu sync.Mutex 263 | 264 | data map[string]string 265 | } 266 | 267 | func NewSMap() *SMap { 268 | return &SMap{ 269 | data: make(map[string]string), 270 | } 271 | } 272 | 273 | func (m *SMap) Get(k string) string { 274 | m.mu.Lock() 275 | defer m.mu.Unlock() 276 | 277 | return m.data[k] 278 | } 279 | ``` 280 | 281 |
Embed mutex para tipos privados o tipos que necesitan implementar la interfaz Mutex.Para tipos públicos, usa un campo privado.
290 | 291 | ### Limitaciones copiando Slices y Maps 292 | 293 | Slices y maps contienen punteros que apuntan a datos subyacentes, así que hay tener cuidado cuando tengamos que copiar sus datos. 294 | 295 | #### Recibiendo Slices y Maps 296 | 297 | Ten presente que un usuario puede modificar un `map` o un `slice` que recibas como argumento si guardas su referencia. 298 | 299 | 300 | 301 | 302 | 303 | 318 | 334 | 335 | 336 | 337 |
Incorrecto Correcto
304 | 305 | ```go 306 | func (d *Driver) SetTrips(trips []Trip) { 307 | d.trips = trips 308 | } 309 | 310 | trips := ... 311 | d1.SetTrips(trips) 312 | 313 | // ¿Querías modificar d1.trips? 314 | trips[0] = ... 315 | ``` 316 | 317 | 319 | 320 | ```go 321 | func (d *Driver) SetTrips(trips []Trip) { 322 | d.trips = make([]Trip, len(trips)) 323 | copy(d.trips, trips) 324 | } 325 | 326 | trips := ... 327 | d1.SetTrips(trips) 328 | 329 | // Ahora podemos modificar trips[0] sin que d1.trips se vea afectado. 330 | trips[0] = ... 331 | ``` 332 | 333 |
338 | 339 | #### Devolviendo Slices y Maps 340 | 341 | Del mismo modo, ten cuidado con las modificaciones que se realicen a los `maps` o `slice` que exponen su estado interno. 342 | 343 | 344 | 345 | 346 | 391 |
IncorrectoCorrecto
347 | 348 | ```go 349 | type Stats struct { 350 | mu sync.Mutex 351 | counters map[string]int 352 | } 353 | 354 | // Snapshot devuelve el valor actual de s.counters. 355 | func (s *Stats) Snapshot() map[string]int { 356 | s.mu.Lock() 357 | defer s.mu.Unlock() 358 | 359 | return s.counters 360 | } 361 | 362 | // snapshot ya no está protegido por el mutex, por eso 363 | // cualquier acceso al snapshot está sujeto a condiciones de carrera. 364 | snapshot := stats.Snapshot() 365 | ``` 366 | 367 | 368 | 369 | ```go 370 | type Stats struct { 371 | mu sync.Mutex 372 | counters map[string]int 373 | } 374 | 375 | func (s *Stats) Snapshot() map[string]int { 376 | s.mu.Lock() 377 | defer s.mu.Unlock() 378 | 379 | result := make(map[string]int, len(s.counters)) 380 | for k, v := range s.counters { 381 | result[k] = v 382 | } 383 | return result 384 | } 385 | 386 | // Snapshot es ahora una copia. 387 | snapshot := stats.Snapshot() 388 | ``` 389 | 390 |
392 | 393 | ### Defer para limpiar 394 | 395 | Utiliza `defer` para limpiar y cerrar recursos como ficheros y `locks`. 396 | 397 | 398 | 399 | 400 | 435 |
IncorrectoCorrecto
401 | 402 | ```go 403 | p.Lock() 404 | if p.count < 10 { 405 | p.Unlock() 406 | return p.count 407 | } 408 | 409 | p.count++ 410 | newCount := p.count 411 | p.Unlock() 412 | 413 | return newCount 414 | 415 | // es sencillo olvidar realizar unlocks cuando tienes múltiples returns 416 | ``` 417 | 418 | 419 | 420 | ```go 421 | p.Lock() 422 | defer p.Unlock() 423 | 424 | if p.count < 10 { 425 | return p.count 426 | } 427 | 428 | p.count++ 429 | return p.count 430 | 431 | // más legible 432 | ``` 433 | 434 |
436 | 437 | El sobrecoste que tiene `defer` es extremadamente pequeño y sólo debe evitarse si puedes 438 | asegurar que el tiempo de ejecución de tu función es de nanosegundos. Se gana mucho más con la legibilidad obtenida utilizando `defer` que el minúsculo coste que tiene utilizarlos. Esto se puede observar claramente en métodos largos que tienen más que simples accesos a memoria, dónde los otros cálculos son mucho más significantes que el `defer`. 439 | 440 | ### El tamaño de los Canales es Uno o Ninguno 441 | 442 | Los canales normalmente deberían ser de tamaño uno o ser `unbuffered`. Por defecto, los canales son 443 | `unbuffered` y tienen un tamaño de cero. Cualquier otro tamaño debería de ser analizado con mucho detalle. 444 | 445 | 446 | 447 | 448 | 465 |
IncorrectoCorrecto
449 | 450 | ```go 451 | // ¡Esta inicialización debería ser suficiente para cualquiera! 452 | c := make(chan int, 64) 453 | ``` 454 | 455 | 456 | 457 | ```go 458 | // Tamaño de uno 459 | c := make(chan int, 1) // o 460 | // Canal unbuffered, tamaño de cero 461 | c := make(chan int) 462 | ``` 463 | 464 |
466 | 467 | ### Empezando Enums en Uno 468 | 469 | La manera estándar de introducir `enums` en Go es declarar un tipo personalizado y un grupo de `const` empezando con el valor `iota`. Como las variables tienen un valor predeterminado de 0, deberías de empezar tus `enums` por un valor que sea distinto de 0. 470 | 471 | 472 | 473 | 474 | 503 |
IncorrectoCorrecto
475 | 476 | ```go 477 | type Operation int 478 | 479 | const ( 480 | Add Operation = iota 481 | Subtract 482 | Multiply 483 | ) 484 | 485 | // Add=0, Subtract=1, Multiply=2 486 | ``` 487 | 488 | 489 | 490 | ```go 491 | type Operation int 492 | 493 | const ( 494 | Add Operation = iota + 1 495 | Subtract 496 | Multiply 497 | ) 498 | 499 | // Add=1, Subtract=2, Multiply=3 500 | ``` 501 | 502 |
504 | 505 | Hay casos donde usar el valor cero tiene sentido, como por ejemplo cuando el valor cero es el comportamiento deseado. 506 | 507 | ```go 508 | type LogOutput int 509 | 510 | const ( 511 | LogToStdout LogOutput = iota 512 | LogToFile 513 | LogToRemote 514 | ) 515 | 516 | // LogToStdout=0, LogToFile=1, LogToRemote=2 517 | ``` 518 | 519 | 520 | 521 | ### Error Types 522 | 523 | Hay varias formas de declarar errores en Go: 524 | 525 | - [`errors.New`] para errores con un simple `string` estático 526 | - [`fmt.Errorf`] para errores con un `string` formateado 527 | - Tipos personalizados que implementan la interfaz `error` es decir tienen el método `Error()` 528 | - Errores envueltos (aka "wrapeados") usando [`"pkg/errors".Wrap`] 529 | 530 | A la hora de devolver errores, hay que considerar ciertos aspecto para tomar la mejor decisión: 531 | 532 | - ¿Es un simple error que no necesita información adicional? Entonces, [`errors.New`] debería ser suficiente. 533 | - ¿Los clientes necesitan detectar y manejar este error? Entonces, deberías de utilizar un tipo personalizado que implemente la interfaz `error`. 534 | - ¿Estás propagando un error que te ha devuelto otra función?Are you propagating an error returned by a downstream function? Entonces, salta a la sección de [error wrapping](#error-wrapping). 535 | - En otros caso utilizaremos, [`fmt.Errorf`]. 536 | 537 | [`errors.New`]: https://golang.org/pkg/errors/#New 538 | [`fmt.Errorf`]: https://golang.org/pkg/fmt/#Errorf 539 | [`"pkg/errors".Wrap`]: https://godoc.org/github.com/pkg/errors#Wrap 540 | 541 | Si el cliente necesita detectar el error pero no necesita información adicional, utiliza errores sentinelas o `sentinel errors` en lugar de devolver la función con [`errors.New`], como se puede ver a continuación: 542 | 543 | 544 | 545 | 546 | 591 |
IncorrectoCorrecto
547 | 548 | ```go 549 | // package foo 550 | 551 | func Open() error { 552 | return errors.New("could not open") 553 | } 554 | 555 | // package bar 556 | 557 | func use() { 558 | if err := foo.Open(); err != nil { 559 | if err.Error() == "could not open" { 560 | // handle 561 | } else { 562 | panic("unknown error") 563 | } 564 | } 565 | } 566 | ``` 567 | 568 | 569 | 570 | ```go 571 | // package foo 572 | 573 | var ErrCouldNotOpen = errors.New("could not open") 574 | 575 | func Open() error { 576 | return ErrCouldNotOpen 577 | } 578 | 579 | // package bar 580 | 581 | if err := foo.Open(); err != nil { 582 | if err == foo.ErrCouldNotOpen { 583 | // handle 584 | } else { 585 | panic("unknown error") 586 | } 587 | } 588 | ``` 589 | 590 |
592 | 593 | Si tienes un error que los clientes necesitan detectar, y además quieres proporcionar más información, entonces deberías utilizar un tipo personalizado. 594 | 595 | 596 | 597 | 598 | 643 |
BadGood
599 | 600 | ```go 601 | func open(file string) error { 602 | return fmt.Errorf("file %q not found", file) 603 | } 604 | 605 | func use() { 606 | if err := open(); err != nil { 607 | if strings.Contains(err.Error(), "not found") { 608 | // handle 609 | } else { 610 | panic("unknown error") 611 | } 612 | } 613 | } 614 | ``` 615 | 616 | 617 | 618 | ```go 619 | type errNotFound struct { 620 | file string 621 | } 622 | 623 | func (e errNotFound) Error() string { 624 | return fmt.Sprintf("file %q not found", e.file) 625 | } 626 | 627 | func open(file string) error { 628 | return errNotFound{file: file} 629 | } 630 | 631 | func use() { 632 | if err := open(); err != nil { 633 | if _, ok := err.(errNotFound); ok { 634 | // handle 635 | } else { 636 | panic("unknown error") 637 | } 638 | } 639 | } 640 | ``` 641 | 642 |
644 | 645 | Ten cuidado con el scope de tus errores de tipos personalizados, ya que si los haces públicos pasaran a ser parte de la API pública del paquete. Es preferible solo exponer una función que compruebe si el error es del tipo deseado, en lugar de exponer tu `struct`. 646 | 647 | ```go 648 | // package foo 649 | 650 | type errNotFound struct { 651 | file string 652 | } 653 | 654 | func (e errNotFound) Error() string { 655 | return fmt.Sprintf("file %q not found", e.file) 656 | } 657 | 658 | func IsNotFoundError(err error) bool { 659 | _, ok := err.(errNotFound) 660 | return ok 661 | } 662 | 663 | func Open(file string) error { 664 | return errNotFound{file: file} 665 | } 666 | 667 | // package bar 668 | 669 | if err := foo.Open("foo"); err != nil { 670 | if foo.IsNotFoundError(err) { 671 | // handle 672 | } else { 673 | panic("unknown error") 674 | } 675 | } 676 | ``` 677 | 678 | 679 | 680 | ### Error Wrapping 681 | 682 | Hay tres formas para propagar los errores si una llamada falla: 683 | 684 | - Devolver el error original si no necesitas añadir un contexto adicional y quieres mantener el tipo del error original. 685 | - Añadir contexto usando [`"pkg/errors".Wrap`] para que el mensaje de error proporcione más contexto y [`"pkg/errors".Cause`] pueda ser usado para extraer el error original. 686 | - Usa [`fmt.Errorf`] si los métodos/funciones no necesitan detectar o manejar este caso específico de error. 687 | 688 | Se recomienda añadir contexto donde sea posible para que en lugar de tener errores como "connection refused", tengamos algo más útil como "call service foo: connection refused". 689 | 690 | Cuando añadimos contexto a los errores devueltos, intentemos mantener el contexto conciso evitando frases como "failed to", que indican lo obvio y se nos acumularán en la pila de mensajes: 691 | 692 | 693 | 694 | 695 | 728 |
IncorrectoCorrecto
696 | 697 | ```go 698 | s, err := store.New() 699 | if err != nil { 700 | return fmt.Errorf( 701 | "failed to create new store: %s", err) 702 | } 703 | ``` 704 | 705 | 706 | 707 | ```go 708 | s, err := store.New() 709 | if err != nil { 710 | return fmt.Errorf( 711 | "new store: %s", err) 712 | } 713 | ``` 714 | 715 |
716 | 717 | ``` 718 | failed to x: failed to y: failed to create new store: the error 719 | ``` 720 | 721 | 722 | 723 | ``` 724 | x: y: new store: the error 725 | ``` 726 | 727 |
729 | 730 | Sin embargo es enviado a otro sistema, deberá de dejar claro que el mensaje es un error (ej. un tag `err` o un prefijo como "Failed" en los logs). 731 | 732 | Un artículo muy interesante sobre el tratamiento de errores en Go: [Don't just check errors, handle them gracefully]. 733 | 734 | [`"pkg/errors".Cause`]: https://godoc.org/github.com/pkg/errors#Cause 735 | [Don't just check errors, handle them gracefully]: https://dave.cheney.net/2016/04/27/dont-just-check-errors-handle-them-gracefully 736 | 737 | ### Manejando los errores de Type Assertion 738 | 739 | *Para claridad de esta parte mantendremos el término original en inglés, `Type Assertion` en lugar de validación de tipos, ya que está más extendido.* 740 | 741 | Devolver en una sola linea un [type assertion] provocará `panic` en tipos incorrectos. Por lo tanto, siempre usaremos la anotación "comma ok". 742 | 743 | [type assertion]: https://golang.org/ref/spec#Type_assertions 744 | 745 | 746 | 747 | 748 | 764 |
IncorrectoCorrecto
749 | 750 | ```go 751 | t := i.(string) 752 | ``` 753 | 754 | 755 | 756 | ```go 757 | t, ok := i.(string) 758 | if !ok { 759 | // handle the error gracefully 760 | } 761 | ``` 762 | 763 |
765 | 766 | 768 | 769 | ### No Panic 770 | 771 | El código que funciona en producción no puede provocar `panics`. Los `panics` son la mayor fuente de [fallos en cascada]. Si un ocurre un error, la función tiene que devolver un error y permitir al que esta usando dicha función como manejarlo. 772 | 773 | [fallos en cascada]: https://en.wikipedia.org/wiki/Cascading_failure 774 | 775 | 776 | 777 | 778 | 820 |
IncorrectoCorrecto
779 | 780 | ```go 781 | func foo(bar string) { 782 | if len(bar) == 0 { 783 | panic("bar must not be empty") 784 | } 785 | // ... 786 | } 787 | 788 | func main() { 789 | if len(os.Args) != 2 { 790 | fmt.Println("USAGE: foo ") 791 | os.Exit(1) 792 | } 793 | foo(os.Args[1]) 794 | } 795 | ``` 796 | 797 | 798 | 799 | ```go 800 | func foo(bar string) error { 801 | if len(bar) == 0 { 802 | return errors.New("bar must not be empty") 803 | } 804 | // ... 805 | return nil 806 | } 807 | 808 | func main() { 809 | if len(os.Args) != 2 { 810 | fmt.Println("USAGE: foo ") 811 | os.Exit(1) 812 | } 813 | if err := foo(os.Args[1]); err != nil { 814 | panic(err) 815 | } 816 | } 817 | ``` 818 | 819 |
821 | 822 | Panic/recover no es una estrategia para controlar errores. Un programa tiene que devolver panic solo cuando sucede algo de lo que no puede recuperarse, como puede ser acceder aun método de un valor `nil`. La excepción a esta regla sólo se aplica cuando inicializamos el programa: aquello que haga que nuestro programa no pueda funcionar correctamente en el momento de inicialización deberá abortar la ejecución debido a un `panic`. 823 | 824 | ```go 825 | var _statusTemplate = template.Must(template.New("name").Parse("_statusHTML")) 826 | ``` 827 | Incluso en los tests, es preferible usar `t.Fatal` o `t.FailNow` en lugar de `panics` para marcar el test como fallido. 828 | 829 | 830 | 831 | 832 | 855 |
IncorrectoCorrecto
833 | 834 | ```go 835 | // func TestFoo(t *testing.T) 836 | 837 | f, err := ioutil.TempFile("", "test") 838 | if err != nil { 839 | panic("failed to set up test") 840 | } 841 | ``` 842 | 843 | 844 | 845 | ```go 846 | // func TestFoo(t *testing.T) 847 | 848 | f, err := ioutil.TempFile("", "test") 849 | if err != nil { 850 | t.Fatal("failed to set up test") 851 | } 852 | ``` 853 | 854 |
856 | 857 | 858 | 859 | ### Usa go.uber.org/atomic 860 | 861 | Las operaciones atómicas con el paquete [sync/atomic] operan en los tipos sin procesar 862 | (`int32`, `int64`, etc.) por lo que es fácil olvidar usar operaciones atómicas para leer o modificar las variables. 863 | 864 | [go.uber.org/atomic] añade seguridad de tipo a estas operaciones ocultando el tipo subyacente. Además, incluye un tipo conveniente, el `atomic.Bool`. 865 | 866 | [go.uber.org/atomic]: https://godoc.org/go.uber.org/atomic 867 | [sync/atomic]: https://golang.org/pkg/sync/atomic/ 868 | 869 | 870 | 871 | 872 | 913 |
IncorrectoCorrecto
873 | 874 | ```go 875 | type foo struct { 876 | running int32 // atomic 877 | } 878 | 879 | func (f* foo) start() { 880 | if atomic.SwapInt32(&f.running, 1) == 1 { 881 | // already running… 882 | return 883 | } 884 | // start the Foo 885 | } 886 | 887 | func (f *foo) isRunning() bool { 888 | return f.running == 1 // race! 889 | } 890 | ``` 891 | 892 | 893 | 894 | ```go 895 | type foo struct { 896 | running atomic.Bool 897 | } 898 | 899 | func (f *foo) start() { 900 | if f.running.Swap(true) { 901 | // already running… 902 | return 903 | } 904 | // start the Foo 905 | } 906 | 907 | func (f *foo) isRunning() bool { 908 | return f.running.Load() 909 | } 910 | ``` 911 | 912 |
914 | 915 | ### Evite globales mutables 916 | 917 | Evite la mutación de variables globales, en su lugar opte por la inyección de dependencias. 918 | Esto se aplica tanto a los punteros de función como a otros tipos de valores. 919 | 920 | 921 | 922 | 923 | 953 | 981 |
BadGood
924 | 925 | ```go 926 | // sign.go 927 | var _timeNow = time.Now 928 | func sign(msg string) string { 929 | now := _timeNow() 930 | return signWithTime(msg, now) 931 | } 932 | ``` 933 | 934 | 935 | 936 | ```go 937 | // sign.go 938 | type signer struct { 939 | now func() time.Time 940 | } 941 | func newSigner() *signer { 942 | return &signer{ 943 | now: time.Now, 944 | } 945 | } 946 | func (s *signer) Sign(msg string) string { 947 | now := s.now() 948 | return signWithTime(msg, now) 949 | } 950 | ``` 951 | 952 |
954 | 955 | ```go 956 | // sign_test.go 957 | func TestSign(t *testing.T) { 958 | oldTimeNow := _timeNow 959 | _timeNow = func() time.Time { 960 | return someFixedTime 961 | } 962 | defer func() { _timeNow = oldTimeNow }() 963 | assert.Equal(t, want, sign(give)) 964 | } 965 | ``` 966 | 967 | 968 | 969 | ```go 970 | // sign_test.go 971 | func TestSigner(t *testing.T) { 972 | s := newSigner() 973 | s.now = func() time.Time { 974 | return someFixedTime 975 | } 976 | assert.Equal(t, want, s.Sign(give)) 977 | } 978 | ``` 979 | 980 |
982 | 983 | ## Rendimiento 984 | 985 | Aqui se recogen las directrices específicas de rendimiento aplicadas 986 | a como realizar ciertas acciones o utilizar ciertas funciones. 987 | 988 | ### Usar strconv en lugar fmt 989 | 990 | Cuando convertimos primitivos a/o strings, `strconv` es más rápido que `fmt`. 991 | 992 | 993 | 994 | 995 | 1012 | 1025 |
IncorrectoCorrecto
996 | 997 | ```go 998 | for i := 0; i < b.N; i++ { 999 | s := fmt.Sprint(rand.Int()) 1000 | } 1001 | ``` 1002 | 1003 | 1004 | 1005 | ```go 1006 | for i := 0; i < b.N; i++ { 1007 | s := strconv.Itoa(rand.Int()) 1008 | } 1009 | ``` 1010 | 1011 |
1013 | 1014 | ``` 1015 | BenchmarkFmtSprint-4 143 ns/op 2 allocs/op 1016 | ``` 1017 | 1018 | 1019 | 1020 | ``` 1021 | BenchmarkStrconv-4 64.2 ns/op 1 allocs/op 1022 | ``` 1023 | 1024 |
1026 | 1027 | ### Evitar convertir strings a bytes 1028 | 1029 | No se recomienda crear `slice` de `byte` a partir de un string de manera repetitiva. En su lugar, 1030 | realiza la conversión una vez y captura el resultado. 1031 | 1032 | 1033 | 1034 | 1035 | 1053 | 1066 |
IncorrectoCorrecto
1036 | 1037 | ```go 1038 | for i := 0; i < b.N; i++ { 1039 | w.Write([]byte("Hello world")) 1040 | } 1041 | ``` 1042 | 1043 | 1044 | 1045 | ```go 1046 | data := []byte("Hello world") 1047 | for i := 0; i < b.N; i++ { 1048 | w.Write(data) 1049 | } 1050 | ``` 1051 | 1052 |
1054 | 1055 | ``` 1056 | BenchmarkBad-4 50000000 22.2 ns/op 1057 | ``` 1058 | 1059 | 1060 | 1061 | ``` 1062 | BenchmarkGood-4 500000000 3.25 ns/op 1063 | ``` 1064 | 1065 |
1067 | 1068 | ### Especificar una capacidad aproximada al Map 1069 | 1070 | Cuando sea posible, inicializacermos el `map` indicando una capacidad aproximada, utilizando la función `make()`. 1071 | 1072 | ```go 1073 | make(map[T1]T2, hint) 1074 | ``` 1075 | 1076 | Intenta siempre que puedas, proporcionar una capacidad aproximada utilizando `make()`, 1077 | ajustando su tamaño a la hora de ser inicializado, esto reduce la necesidad de aumentar 1078 | el tamaño y las asignaciones del `map` a medida que se vayan agregando nuevos elementos. 1079 | Ten en cuenta que la capacidad aproximada no impedirá en ningún caso que se puedan asignar 1080 | elementos de más aunque ésta haya sido proporcionada. 1081 | 1082 | 1083 | 1084 | 1085 | 1109 | 1120 |
IncorrectoCorrecto
1086 | 1087 | ```go 1088 | m := make(map[string]os.FileInfo) 1089 | 1090 | files, _ := ioutil.ReadDir("./files") 1091 | for _, f := range files { 1092 | m[f.Name()] = f 1093 | } 1094 | ``` 1095 | 1096 | 1097 | 1098 | ```go 1099 | 1100 | files, _ := ioutil.ReadDir("./files") 1101 | 1102 | m := make(map[string]os.FileInfo, len(files)) 1103 | for _, f := range files { 1104 | m[f.Name()] = f 1105 | } 1106 | ``` 1107 | 1108 |
1110 | 1111 | `m` es creado sin asignar una capacidad previa; 1112 | esto provocará más asignaciones en el momento de añadir nuevos elementos. 1113 | 1114 | 1115 | 1116 | `m` es creado utilizando una capacidad previa; habrá menos asignaciones en el momento de añadir 1117 | nuevos elementos. 1118 | 1119 |
1121 | 1122 | ## Estilo 1123 | 1124 | ### Se consistente 1125 | 1126 | Alguna de las directrices recogidas en este documente pueden ser tratadas de manera objetiva; sin embargo otras son 1127 | situacionales, contextuales o subjetivas. 1128 | 1129 | Por encima de todo, **se consistente**. 1130 | 1131 | El código consistente es sencillo de mantener, sencillo de comprender, 1132 | requiere menos esfuerzo cognitivo y es más sencillo de migrar o actualizar si aparecen nuevas 1133 | convenciones o funcionalidades o bugs que solucionar. 1134 | 1135 | Por el contrario, tener estilos dispares o conflictivos entre si en un solo repositorio de código, causará sin duda 1136 | alguna un sobrecoste de mantenimiento, que afectará de manera significativa a la velocidad, code reviews y creación de 1137 | nuevas funcionalidades y corrección de errores. 1138 | 1139 | Cuando apliques estas directrices en tu código, es recomendable que los cambios sean hechos a nivel de paquete 1140 | (o en todo el código), de otro modo la aplicación a nivel de sub paquetes violará la preocupación anterior al 1141 | introducir múltiples estilos en el mismo código. 1142 | 1143 | ### Agrupar declaraciones similares 1144 | 1145 | Go soporta la agrupación de declaraciones similares. 1146 | 1147 | 1148 | 1149 | 1150 | 1167 |
IncorrectoCorrecto
1151 | 1152 | ```go 1153 | import "a" 1154 | import "b" 1155 | ``` 1156 | 1157 | 1158 | 1159 | ```go 1160 | import ( 1161 | "a" 1162 | "b" 1163 | ) 1164 | ``` 1165 | 1166 |
1168 | 1169 | Esto también aplica a variables, constantes y declaraciones de tipo. 1170 | 1171 | 1172 | 1173 | 1174 | 1212 |
IncorrectoCorrecto
1175 | 1176 | ```go 1177 | 1178 | const a = 1 1179 | const b = 2 1180 | 1181 | 1182 | 1183 | var a = 1 1184 | var b = 2 1185 | 1186 | 1187 | 1188 | type Area float64 1189 | type Volume float64 1190 | ``` 1191 | 1192 | 1193 | 1194 | ```go 1195 | const ( 1196 | a = 1 1197 | b = 2 1198 | ) 1199 | 1200 | var ( 1201 | a = 1 1202 | b = 2 1203 | ) 1204 | 1205 | type ( 1206 | Area float64 1207 | Volume float64 1208 | ) 1209 | ``` 1210 | 1211 |
1213 | 1214 | Sólo agruparemos aquellas declaraciones que estén estrechamente cohesionadas. 1215 | 1216 | 1217 | 1218 | 1219 | 1247 |
IncorrectoCorrecto
1220 | 1221 | ```go 1222 | type Operation int 1223 | 1224 | const ( 1225 | Add Operation = iota + 1 1226 | Subtract 1227 | Multiply 1228 | ENV_VAR = "MY_ENV" 1229 | ) 1230 | ``` 1231 | 1232 | 1233 | 1234 | ```go 1235 | type Operation int 1236 | 1237 | const ( 1238 | Add Operation = iota + 1 1239 | Subtract 1240 | Multiply 1241 | ) 1242 | 1243 | const ENV_VAR = "MY_ENV" 1244 | ``` 1245 | 1246 |
1248 | 1249 | Las agrupaciones no tienen limitaciones en cuanto a donde pueden ser usadas. Podemos utilizarlas incluso dentro de una función. 1250 | 1251 | 1252 | 1253 | 1254 | 1281 |
IncorrectoCorrecto
1255 | 1256 | ```go 1257 | func f() string { 1258 | var red = color.New(0xff0000) 1259 | var green = color.New(0x00ff00) 1260 | var blue = color.New(0x0000ff) 1261 | 1262 | ... 1263 | } 1264 | ``` 1265 | 1266 | 1267 | 1268 | ```go 1269 | func f() string { 1270 | var ( 1271 | red = color.New(0xff0000) 1272 | green = color.New(0x00ff00) 1273 | blue = color.New(0x0000ff) 1274 | ) 1275 | 1276 | ... 1277 | } 1278 | ``` 1279 | 1280 |
1282 | 1283 | ### Agrupaciones y orden en los imports 1284 | 1285 | Debería haber siempre sólo dos grupos de `imports`: 1286 | 1287 | - Paquete estándar 1288 | - Todo lo demás 1289 | 1290 | Esta es la forma que aplica `goimports` por defecto. 1291 | 1292 | 1293 | 1294 | 1295 | 1319 |
IncorrectoCorrecto
1296 | 1297 | ```go 1298 | import ( 1299 | "fmt" 1300 | "os" 1301 | "go.uber.org/atomic" 1302 | "golang.org/x/sync/errgroup" 1303 | ) 1304 | ``` 1305 | 1306 | 1307 | 1308 | ```go 1309 | import ( 1310 | "fmt" 1311 | "os" 1312 | 1313 | "go.uber.org/atomic" 1314 | "golang.org/x/sync/errgroup" 1315 | ) 1316 | ``` 1317 | 1318 |
1320 | 1321 | ### Nombre de paquetes 1322 | 1323 | Cuando nombramos un paquete, debemos seguir las siguientes normas: 1324 | 1325 | - Será completamente en minúscula. No habrá ninguna mayúscula para separar palabras ni guiones bajo. 1326 | - No debería de necesitar el uso de alias cuando sea importado. 1327 | - Debe ser corto y conciso. Recuerda que ese nombre será el identificador del paquete en cada llamada. 1328 | - No usar pluares. Por ejemplo, `net\url`, no `net\urls`. 1329 | - No utilizar nombres de paquetes como, `common`, `util`, `shared`, o `lib`. Son nombres malos y que no dicen nada. 1330 | 1331 | Ver también [Package Names] y [Style guideline for Go packages]. 1332 | 1333 | [Package Names]: https://blog.golang.org/package-names 1334 | [Style guideline for Go packages]: https://rakyll.org/style-packages/ 1335 | 1336 | ### Nombres de funciones 1337 | 1338 | Seguimos las pautas de la comunidad de Go [MixedCaps for function names]. La única excepción es para las funciones de los test, las cuales pueden contener guiones bajos con el propósito agrupar los casos de los tests. ej., `TestMyFunction_WhatIsBeingTested`. 1339 | 1340 | [MixedCaps for function names]: https://golang.org/doc/effective_go.html#mixed-caps 1341 | 1342 | ### Alias en los imports 1343 | 1344 | Los alias para los imports sólo deben ser usados si el nombre no coincide con el último elemento de la ruta importada. 1345 | 1346 | ```go 1347 | import ( 1348 | "net/http" 1349 | 1350 | client "example.com/client-go" 1351 | trace "example.com/trace/v2" 1352 | ) 1353 | ``` 1354 | En todos los demás escenarios, los alias para los imports deben ser evitados a no ser que haya un conflicto directo entre los paquetes. 1355 | 1356 | 1357 | 1358 | 1359 | 1384 |
IncorrectoCorrecto
1360 | 1361 | ```go 1362 | import ( 1363 | "fmt" 1364 | "os" 1365 | 1366 | 1367 | nettrace "golang.net/x/trace" 1368 | ) 1369 | ``` 1370 | 1371 | 1372 | 1373 | ```go 1374 | import ( 1375 | "fmt" 1376 | "os" 1377 | "runtime/trace" 1378 | 1379 | nettrace "golang.net/x/trace" 1380 | ) 1381 | ``` 1382 | 1383 |
1385 | 1386 | ### Agrupación de funciones y orden 1387 | 1388 | - Las funciones deben agruparse por proximidad de llamada. 1389 | - Las funciones de un fichero deben agruparse por su receptor. 1390 | 1391 | Por lo tanto, las funciones públicas aparecerán al principio del fichero, después de las definiciones de `struct`, `const`, `var`. 1392 | 1393 | El constructor `newXYZ()`/`NewXYZ()` debe aparecer después de que el tipo sea definido, pero antes del resto de métodos del receptor. 1394 | 1395 | Como las funciones están agrupadas por receptor, las funciones de tipo *helper* deberían aparecer al final del fichero. 1396 | 1397 | 1398 | 1399 | 1400 | 1437 |
IncorrectoCorrecto
1401 | 1402 | ```go 1403 | func (s *something) Cost() { 1404 | return calcCost(s.weights) 1405 | } 1406 | 1407 | type something struct{ ... } 1408 | 1409 | func calcCost(n []int) int {...} 1410 | 1411 | func (s *something) Stop() {...} 1412 | 1413 | func newSomething() *something { 1414 | return &something{} 1415 | } 1416 | ``` 1417 | 1418 | 1419 | 1420 | ```go 1421 | type something struct{ ... } 1422 | 1423 | func newSomething() *something { 1424 | return &something{} 1425 | } 1426 | 1427 | func (s *something) Cost() { 1428 | return calcCost(s.weights) 1429 | } 1430 | 1431 | func (s *something) Stop() {...} 1432 | 1433 | func calcCost(n []int) int {...} 1434 | ``` 1435 | 1436 |
1438 | 1439 | ### Reducir el código anidado 1440 | 1441 | Tenemos que tener un código con poca anidación, donde pondremos el control de errores y condiciones de validación al principio provocando un return temprano o continuar un bucle. Reduciendo el número de código anidado a varios niveles. 1442 | 1443 | 1444 | 1445 | 1446 | 1481 |
IncorrectoCorrecto
1447 | 1448 | ```go 1449 | for _, v := range data { 1450 | if v.F1 == 1 { 1451 | v = process(v) 1452 | if err := v.Call(); err == nil { 1453 | v.Send() 1454 | } else { 1455 | return err 1456 | } 1457 | } else { 1458 | log.Printf("Invalid v: %v", v) 1459 | } 1460 | } 1461 | ``` 1462 | 1463 | 1464 | 1465 | ```go 1466 | for _, v := range data { 1467 | if v.F1 != 1 { 1468 | log.Printf("Invalid v: %v", v) 1469 | continue 1470 | } 1471 | 1472 | v = process(v) 1473 | if err := v.Call(); err != nil { 1474 | return err 1475 | } 1476 | v.Send() 1477 | } 1478 | ``` 1479 | 1480 |
1482 | 1483 | ### Else innecesario 1484 | Si una variable es asignada tanto en el `if` como en el `else`, esto podrá ser remplazado por un único `if`. 1485 | 1486 | 1487 | 1488 | 1489 | 1510 |
IncorrectoCorrecto
1490 | 1491 | ```go 1492 | var a int 1493 | if b { 1494 | a = 100 1495 | } else { 1496 | a = 10 1497 | } 1498 | ``` 1499 | 1500 | 1501 | 1502 | ```go 1503 | a := 10 1504 | if b { 1505 | a = 100 1506 | } 1507 | ``` 1508 | 1509 |
1511 | 1512 | ### Declaración de variables globales 1513 | 1514 | Las variables globales usan la palabra reservada `var`. No es necesario especificar el tipo, a no ser que sea una declaración de un tipo diferente que la expresión. 1515 | 1516 | 1517 | 1518 | 1519 | 1538 |
IncorrectoCorrecto
1520 | 1521 | ```go 1522 | var _s string = F() 1523 | 1524 | func F() string { return "A" } 1525 | ``` 1526 | 1527 | 1528 | 1529 | ```go 1530 | var _s = F() 1531 | // Como F ya indica que devuelve una cadena, no hace falta especificar 1532 | // el tipo de nuevo. 1533 | 1534 | func F() string { return "A" } 1535 | ``` 1536 | 1537 |
1539 | 1540 | Especifica el tipo si la expresión no es exactamente el tipo que buscas. 1541 | 1542 | ```go 1543 | type myError struct{} 1544 | 1545 | func (myError) Error() string { return "error" } 1546 | 1547 | func F() myError { return myError{} } 1548 | 1549 | var _e error = F() 1550 | // F returns an object of type myError but we want error. 1551 | ``` 1552 | 1553 | ### Prefijo _ para las globales privadas 1554 | 1555 | Todas las variables, `var` y constantes, `const` globales irán acompañadas del prefijo `_` para clarificar cuando se usan que son globales. 1556 | 1557 | Excepción: los errores privados, deben ir acompañados del prefijo `err`. 1558 | 1559 | Justificación: Las variables y constantes globales tienen el scope del paquete. Usando un nombre muy genérico se pueden provocar accidentes muy fácilmente. 1560 | 1561 | 1562 | 1563 | 1564 | 1598 |
IncorrectoCorrecto
1565 | 1566 | ```go 1567 | // foo.go 1568 | 1569 | const ( 1570 | defaultPort = 8080 1571 | defaultUser = "user" 1572 | ) 1573 | 1574 | // bar.go 1575 | 1576 | func Bar() { 1577 | defaultPort := 9090 1578 | ... 1579 | fmt.Println("Default port", defaultPort) 1580 | 1581 | // No veremos error de compilación si la primera línea de 1582 | // Bar() es borrada. 1583 | } 1584 | ``` 1585 | 1586 | 1587 | 1588 | ```go 1589 | // foo.go 1590 | 1591 | const ( 1592 | _defaultPort = 8080 1593 | _defaultUser = "user" 1594 | ) 1595 | ``` 1596 | 1597 |
1599 | 1600 | ### Composición en Structs 1601 | 1602 | *Embedded types* (como los mutexs) deben estar al principio de la lista del `struct`, y deben añadir un salto de línea para separarlos de los campos regulares del `struct`. 1603 | 1604 | 1605 | 1606 | 1607 | 1627 |
IncorrectoCorrecto
1608 | 1609 | ```go 1610 | type Client struct { 1611 | version int 1612 | http.Client 1613 | } 1614 | ``` 1615 | 1616 | 1617 | 1618 | ```go 1619 | type Client struct { 1620 | http.Client 1621 | 1622 | version int 1623 | } 1624 | ``` 1625 | 1626 |
1628 | 1629 | ### Usa el nombre de los campos al inicializar Structs 1630 | 1631 | Debes especificar los campos del `struct` cuando vayas a inicializarlo. Además esto es ahora de cumplimiento obligatorio utilizando [`go vet`]. 1632 | 1633 | [`go vet`]: https://golang.org/cmd/vet/ 1634 | 1635 | 1636 | 1637 | 1638 | 1655 |
IncorrectoCorrecto
1639 | 1640 | ```go 1641 | k := User{"John", "Doe", true} 1642 | ``` 1643 | 1644 | 1645 | 1646 | ```go 1647 | k := User{ 1648 | FirstName: "John", 1649 | LastName: "Doe", 1650 | Admin: true, 1651 | } 1652 | ``` 1653 | 1654 |
1656 | 1657 | Excepción: Puedes omitir inicializar un `struct` con el nombre de sus campos en los *test tables* cuando tengan 3 o menos campos. 1658 | 1659 | ```go 1660 | tests := []struct{ 1661 | op Operation 1662 | want string 1663 | }{ 1664 | {Add, "add"}, 1665 | {Subtract, "subtract"}, 1666 | } 1667 | ``` 1668 | 1669 | ### Declaración de variables locales 1670 | 1671 | La forma corta de declaración de variables (`:=`) debe ser usada si una variable se le va a asignar un valor explícito. 1672 | 1673 | 1674 | 1675 | 1676 | 1689 |
IncorrectoCorrecto
1677 | 1678 | ```go 1679 | var s = "foo" 1680 | ``` 1681 | 1682 | 1683 | 1684 | ```go 1685 | s := "foo" 1686 | ``` 1687 | 1688 |
1690 | 1691 | Sin embargo, hay algunos casos donde el valor por defecto queda más claro utilizando `var`. [Declarando Slices vacíos], por ejemplo. 1692 | 1693 | [Declarando Slices vacíos]: https://github.com/golang/go/wiki/CodeReviewComments#declaring-empty-slices 1694 | 1695 | 1696 | 1697 | 1698 | 1725 |
IncorrectoCorrecto
1699 | 1700 | ```go 1701 | func f(list []int) { 1702 | filtered := []int{} 1703 | for _, v := range list { 1704 | if v > 10 { 1705 | filtered = append(filtered, v) 1706 | } 1707 | } 1708 | } 1709 | ``` 1710 | 1711 | 1712 | 1713 | ```go 1714 | func f(list []int) { 1715 | var filtered []int 1716 | for _, v := range list { 1717 | if v > 10 { 1718 | filtered = append(filtered, v) 1719 | } 1720 | } 1721 | } 1722 | ``` 1723 | 1724 |
1726 | 1727 | ### nil es un slice válido 1728 | 1729 | `nil` es un `slice` válido de tamaño 0. Eso quiere decir que, 1730 | 1731 | - No debes devolver un `slice` de tamaño 0 explícitamente. Devuelve `nil` en su lugar. 1732 | 1733 | 1734 | 1735 | 1736 | 1753 |
IncorrectoCorrecto
1737 | 1738 | ```go 1739 | if x == "" { 1740 | return []int{} 1741 | } 1742 | ``` 1743 | 1744 | 1745 | 1746 | ```go 1747 | if x == "" { 1748 | return nil 1749 | } 1750 | ``` 1751 | 1752 |
1754 | 1755 | - Para comprobar si un `slice` está vacío, siempre usaremos `len(s) == 0`. No comprobaremos si es `nil`. 1756 | 1757 | 1758 | 1759 | 1776 |
IncorrectoCorrecto
1760 | 1761 | ```go 1762 | func isEmpty(s []string) bool { 1763 | return s == nil 1764 | } 1765 | ``` 1766 | 1767 | 1768 | 1769 | ```go 1770 | func isEmpty(s []string) bool { 1771 | return len(s) == 0 1772 | } 1773 | ``` 1774 | 1775 |
1777 | 1778 | - Cuando declaramos un slice con `var`, es decir declararlo a su valor inicial, éste es usable inmediatamente sin necesidad de `make()`. 1779 | 1780 | 1781 | 1782 | 1783 | 1813 |
IncorrectoCorrecto
1784 | 1785 | ```go 1786 | nums := []int{} 1787 | // or, nums := make([]int) 1788 | 1789 | if add1 { 1790 | nums = append(nums, 1) 1791 | } 1792 | 1793 | if add2 { 1794 | nums = append(nums, 2) 1795 | } 1796 | ``` 1797 | 1798 | 1799 | 1800 | ```go 1801 | var nums []int 1802 | 1803 | if add1 { 1804 | nums = append(nums, 1) 1805 | } 1806 | 1807 | if add2 { 1808 | nums = append(nums, 2) 1809 | } 1810 | ``` 1811 | 1812 |
1814 | 1815 | ### Reducir el scope de las variables 1816 | 1817 | Cuando sea posible, reducir el scope de las variables. No hay que reducir el scope si entramos en conflicto con la regla [Reducir el código anidado](#reducir-el-cdigo-anidado). 1818 | 1819 | 1820 | 1821 | 1822 | 1840 |
IncorrectoCorrecto
1823 | 1824 | ```go 1825 | err := ioutil.WriteFile(name, data, 0644) 1826 | if err != nil { 1827 | return err 1828 | } 1829 | ``` 1830 | 1831 | 1832 | 1833 | ```go 1834 | if err := ioutil.WriteFile(name, data, 0644); err != nil { 1835 | return err 1836 | } 1837 | ``` 1838 | 1839 |
1841 | 1842 | Si necesitas un resultado de una función fuera del `if`, entonces no debes de intentar reducir el scope. 1843 | 1844 | 1845 | 1846 | 1847 | 1880 |
IncorrectoCorrecto
1848 | 1849 | ```go 1850 | if data, err := ioutil.ReadFile(name); err == nil { 1851 | err = cfg.Decode(data) 1852 | if err != nil { 1853 | return err 1854 | } 1855 | 1856 | fmt.Println(cfg) 1857 | return nil 1858 | } else { 1859 | return err 1860 | } 1861 | ``` 1862 | 1863 | 1864 | 1865 | ```go 1866 | data, err := ioutil.ReadFile(name) 1867 | if err != nil { 1868 | return err 1869 | } 1870 | 1871 | if err := cfg.Decode(data); err != nil { 1872 | return err 1873 | } 1874 | 1875 | fmt.Println(cfg) 1876 | return nil 1877 | ``` 1878 | 1879 |
1881 | 1882 | ### Evitar parámetros planos 1883 | 1884 | Los parámetros planos en una función pueden dificultar la legibilidad. Para solucionar esto añadiremos los comentarios al estilo `C`, (`/* ... */`), para añadir el nombre de los parámetros cuando estos no sean obvios. 1885 | 1886 | 1887 | 1888 | 1889 | 1906 |
IncorrectoCorrecto
1890 | 1891 | ```go 1892 | // func printInfo(name string, isLocal, done bool) 1893 | 1894 | printInfo("foo", true, true) 1895 | ``` 1896 | 1897 | 1898 | 1899 | ```go 1900 | // func printInfo(name string, isLocal, done bool) 1901 | 1902 | printInfo("foo", true /* isLocal */, true /* done */) 1903 | ``` 1904 | 1905 |
1907 | 1908 | Todavía mejor, si remplazamos el `bool` por un tipo personalizado más legible y seguro. Además esto nos permitirá añadir nuevos estados que sólo (`true`/`false`) en el futuro. 1909 | 1910 | ```go 1911 | type Region int 1912 | 1913 | const ( 1914 | UnknownRegion Region = iota 1915 | Local 1916 | ) 1917 | 1918 | type Status int 1919 | 1920 | const ( 1921 | StatusReady = iota + 1 1922 | StatusDone 1923 | // Maybe we will have a StatusInProgress in the future. 1924 | ) 1925 | 1926 | func printInfo(name string, region Region, status Status) 1927 | ``` 1928 | 1929 | ### Evita declarar strings con escapados 1930 | 1931 | Go soporta los llamados [raw string literals](https://golang.org/ref/spec#raw_string_lit), 1932 | los cuales permiten múltiples lineas y comillas, usa este tipo de `string` para evitar tener que escapar tu cadena y que la haga más complicada de leer. 1933 | 1934 | 1935 | 1936 | 1937 | 1950 |
IncorrectoCorrecto
1938 | 1939 | ```go 1940 | wantError := "unknown name:\"test\"" 1941 | ``` 1942 | 1943 | 1944 | 1945 | ```go 1946 | wantError := `unknown error:"test"` 1947 | ``` 1948 | 1949 |
1951 | 1952 | ### Inicializando referencias a Struct 1953 | 1954 | Usa siempre `&T{}` en lugar de `new(T)` cuando realices una inicialización por referencia de un `struct` a modo de ser consistente con la inicialización del `struct`. 1955 | 1956 | 1957 | 1958 | 1959 | 1978 |
IncorrectoCorrecto
1960 | 1961 | ```go 1962 | sval := T{Name: "foo"} 1963 | 1964 | // inconsistent 1965 | sptr := new(T) 1966 | sptr.Name = "bar" 1967 | ``` 1968 | 1969 | 1970 | 1971 | ```go 1972 | sval := T{Name: "foo"} 1973 | 1974 | sptr := &T{Name: "bar"} 1975 | ``` 1976 | 1977 |
1979 | 1980 | ### Inicializando Maps 1981 | 1982 | Elige `make(..)` para `maps` vacíos, y `maps` que sean llenados programáticamente. 1983 | Esto hace que la inicialización del `map` sea distinta de la declaración visualmente, haciendo que sea más fácil agregar capacidad en un futuro si fuera necesario. 1984 | 1985 | 1986 | 1987 | 1988 | 2011 | 2020 |
IncorrectoCorrecto
1989 | 1990 | ```go 1991 | var ( 1992 | // m1 is safe to read and write; 1993 | // m2 will panic on writes. 1994 | m1 = map[T1]T2{} 1995 | m2 map[T1]T2 1996 | ) 1997 | ``` 1998 | 1999 | 2000 | 2001 | ```go 2002 | var ( 2003 | // m1 is safe to read and write; 2004 | // m2 will panic on writes. 2005 | m1 = make(map[T1]T2) 2006 | m2 map[T1]T2 2007 | ) 2008 | ``` 2009 | 2010 |
2012 | 2013 | La declaración y la inicialización es visualmente similar. 2014 | 2015 | 2016 | 2017 | La declaración y la inicialización es visualmente distinta. 2018 | 2019 |
2021 | 2022 | Donde sea posible, añade la capacidad cuando inicialices los `maps` con `make()`. Más información: [Especificar una capacidad aproximada al Map](#especificar-una-capacidad-aproximada-al-map). 2023 | 2024 | Por otro lado, si el `map` tiene un tamaño fijo de elementos, declara literalmente el `map` al inicializarlo. 2025 | 2026 | 2027 | 2028 | 2029 | 2049 |
IncorrectoCorrecto
2030 | 2031 | ```go 2032 | m := make(map[T1]T2, 3) 2033 | m[k1] = v1 2034 | m[k2] = v2 2035 | m[k3] = v3 2036 | ``` 2037 | 2038 | 2039 | 2040 | ```go 2041 | m := map[T1]T2{ 2042 | k1: v1, 2043 | k2: v2, 2044 | k3: v3, 2045 | } 2046 | ``` 2047 | 2048 |
2050 | 2051 | La regla de oro es utilizar la declaración literal de los `maps`, cuando añadas un grupo fijo de elementos en el momento de inicialización, de otro modo usa `make` (y especifica la capacidad si es posible). 2052 | 2053 | ### Formatos de Strings fuera del Printf 2054 | 2055 | Si declaras un formato de `string` con el estilo del `Printf` fuera de una cadena, entonces debes hacerlo con una constante. 2056 | 2057 | Esto ayudará a `go vet` a realizar un análisis estático del formato del `string`. 2058 | 2059 | 2060 | 2061 | 2062 | 2077 |
IncorrectoCorrecto
2063 | 2064 | ```go 2065 | msg := "unexpected values %v, %v\n" 2066 | fmt.Printf(msg, 1, 2) 2067 | ``` 2068 | 2069 | 2070 | 2071 | ```go 2072 | const msg = "unexpected values %v, %v\n" 2073 | fmt.Printf(msg, 1, 2) 2074 | ``` 2075 | 2076 |
2078 | 2079 | ### Nombra funciones al estilo Printf 2080 | 2081 | Cuando declares una función de estilo `Printf`, asegúrate de que `go vet` pueda detectarla y comprobar el formato del `string`. 2082 | 2083 | Esto quiere decir que debes usar los nombres al estilo `Printf` si es posible. `go vet` comprobará esto por defecto. Más información: [Printf family] 2084 | 2085 | [Printf family]: https://golang.org/cmd/vet/#hdr-Printf_family 2086 | 2087 | Si no puedes utilizar los nombres predifinidos, acaba tus funciones con `f`: `Wrapf`, no `Wrap`. A `go vet` se le puede especificar que compruebe como funciones de estilo `Printf`, aquellas funciones que su nombre acabe por `f`. 2088 | 2089 | ```shell 2090 | $ go vet -printfuncs=wrapf,statusf 2091 | ``` 2092 | 2093 | Más información: [go vet: Printf family check]. 2094 | 2095 | [go vet: Printf family check]: https://kuzminva.wordpress.com/2017/11/07/go-vet-printf-family-check/ 2096 | 2097 | ## Patrones 2098 | 2099 | ### Test Tables 2100 | 2101 | Usa *table-driven tests* con [subtests] para evitar la duplicación de código cuando la lógica del test es repetitiva. 2102 | 2103 | [subtests]: https://blog.golang.org/subtests 2104 | 2105 | 2106 | 2107 | 2108 | 2177 |
IncorrectoCorrecto
2109 | 2110 | ```go 2111 | // func TestSplitHostPort(t *testing.T) 2112 | 2113 | host, port, err := net.SplitHostPort("192.0.2.0:8000") 2114 | require.NoError(t, err) 2115 | assert.Equal(t, "192.0.2.0", host) 2116 | assert.Equal(t, "8000", port) 2117 | 2118 | host, port, err = net.SplitHostPort("192.0.2.0:http") 2119 | require.NoError(t, err) 2120 | assert.Equal(t, "192.0.2.0", host) 2121 | assert.Equal(t, "http", port) 2122 | 2123 | host, port, err = net.SplitHostPort(":8000") 2124 | require.NoError(t, err) 2125 | assert.Equal(t, "", host) 2126 | assert.Equal(t, "8000", port) 2127 | 2128 | host, port, err = net.SplitHostPort("1:8") 2129 | require.NoError(t, err) 2130 | assert.Equal(t, "1", host) 2131 | assert.Equal(t, "8", port) 2132 | ``` 2133 | 2134 | 2135 | 2136 | ```go 2137 | // func TestSplitHostPort(t *testing.T) 2138 | 2139 | tests := []struct{ 2140 | give string 2141 | wantHost string 2142 | wantPort string 2143 | }{ 2144 | { 2145 | give: "192.0.2.0:8000", 2146 | wantHost: "192.0.2.0", 2147 | wantPort: "8000", 2148 | }, 2149 | { 2150 | give: "192.0.2.0:http", 2151 | wantHost: "192.0.2.0", 2152 | wantPort: "http", 2153 | }, 2154 | { 2155 | give: ":8000", 2156 | wantHost: "", 2157 | wantPort: "8000", 2158 | }, 2159 | { 2160 | give: "1:8", 2161 | wantHost: "1", 2162 | wantPort: "8", 2163 | }, 2164 | } 2165 | 2166 | for _, tt := range tests { 2167 | t.Run(tt.give, func(t *testing.T) { 2168 | host, port, err := net.SplitHostPort(tt.give) 2169 | require.NoError(t, err) 2170 | assert.Equal(t, tt.wantHost, host) 2171 | assert.Equal(t, tt.wantPort, port) 2172 | }) 2173 | } 2174 | ``` 2175 | 2176 |
2178 | 2179 | *Test tables* hace mucho más sencillo añadir contexto a mensajes de errores, reduce la lógica duplicada y añade nuevos casos de test.s. 2180 | 2181 | Seguimos la convención de el `slice` de `structs` se le llamará `tests` y cada caso de test `tt`. Además explicitamos los valores de *input*(entrada) y *output*(salida) de cada test con los prefijos `give` y `want`. 2182 | 2183 | ```go 2184 | tests := []struct{ 2185 | give string 2186 | wantHost string 2187 | wantPort string 2188 | }{ 2189 | // ... 2190 | } 2191 | 2192 | for _, tt := range tests { 2193 | // ... 2194 | } 2195 | ``` 2196 | 2197 | ### Opciones funcionales 2198 | 2199 | *Functional options* es un patrón donde declaras un tipo `Option` que almacena la información en algún `struct` interno. Puedes aceptar un número variable de estas opciones y actuar según la información almacenada por las `options` en el `struct` interno. 2200 | 2201 | Es recomendable utilizar este patrón para argumentos opcionales en los constructores y otras funciones públicas de tu API cuando prevés la necesidad de expandirlos, especialmente si ya tienes tres o más argumentos en dicha función. 2202 | 2203 | 2204 | 2205 | 2206 | 2247 | 2275 |
BadGood
2207 | 2208 | ```go 2209 | // package db 2210 | 2211 | func Open( 2212 | addr string, 2213 | cache bool, 2214 | logger *zap.Logger 2215 | ) (*Connection, error) { 2216 | // ... 2217 | } 2218 | ``` 2219 | 2220 | 2221 | 2222 | ```go 2223 | // package db 2224 | 2225 | type Option interface { 2226 | // ... 2227 | } 2228 | 2229 | func WithCache(c bool) Option { 2230 | // ... 2231 | } 2232 | 2233 | func WithLogger(log *zap.Logger) Option { 2234 | // ... 2235 | } 2236 | 2237 | // Open creates a connection. 2238 | func Open( 2239 | addr string, 2240 | opts ...Option, 2241 | ) (*Connection, error) { 2242 | // ... 2243 | } 2244 | ``` 2245 | 2246 |
2248 | 2249 | Los parámetros de `cache` y `logger` siempre deben proporcionarse, incluso si el usuario 2250 | quiere usar el predeterminado. 2251 | 2252 | ```go 2253 | db.Open(addr, db.DefaultCache, zap.NewNop()) 2254 | db.Open(addr, db.DefaultCache, log) 2255 | db.Open(addr, false /* cache */, zap.NewNop()) 2256 | db.Open(addr, false /* cache */, log) 2257 | ``` 2258 | 2259 | 2260 | 2261 | Las opciones son proporcionadas solo si son necesarias. 2262 | 2263 | ```go 2264 | db.Open(addr) 2265 | db.Open(addr, db.WithLogger(log)) 2266 | db.Open(addr, db.WithCache(false)) 2267 | db.Open( 2268 | addr, 2269 | db.WithCache(false), 2270 | db.WithLogger(log), 2271 | ) 2272 | ``` 2273 | 2274 |
2276 | 2277 | La forma que nosotros sugerimos de implementar este patrón es con una interfaz `Option` 2278 | que contiene un método no exportado, grabando las opciones en un struct no exportado 2279 | de nombre `options`. 2280 | 2281 | ```go 2282 | type options struct { 2283 | cache bool 2284 | logger *zap.Logger 2285 | } 2286 | 2287 | type Option interface { 2288 | apply(*options) 2289 | } 2290 | 2291 | type cacheOption bool 2292 | 2293 | func (c cacheOption) apply(opts *options) { 2294 | opts.cache = bool(c) 2295 | } 2296 | 2297 | func WithCache(c bool) Option { 2298 | return cacheOption(c) 2299 | } 2300 | 2301 | type loggerOption struct { 2302 | Log *zap.Logger 2303 | } 2304 | 2305 | func (l loggerOption) apply(opts *options) { 2306 | opts.logger = l.Log 2307 | } 2308 | 2309 | func WithLogger(log *zap.Logger) Option { 2310 | return loggerOption{Log: log} 2311 | } 2312 | 2313 | // Open creates a connection. 2314 | func Open( 2315 | addr string, 2316 | opts ...Option, 2317 | ) (*Connection, error) { 2318 | options := options{ 2319 | cache: defaultCache, 2320 | logger: zap.NewNop(), 2321 | } 2322 | 2323 | for _, o := range opts { 2324 | o.apply(&options) 2325 | } 2326 | 2327 | // ... 2328 | } 2329 | ``` 2330 | 2331 | Tenga en cuenta que existe un método para implementar este patrón con `closures`, pero 2332 | creemos que el patrón anterior proporciona más flexibilidad para los autores y es 2333 | más fácil de depurar y probar para los usuarios. En particular, permite que las opciones sean 2334 | comparadas entre sí en `test` y `mocks`, versus `closures` donde esto es 2335 | imposible. Además, permite que las opciones implementen otras interfaces, incluidas 2336 | `fmt.Stringer` que permite representaciones de `string` legibles por el usuario. 2337 | 2338 | Recomendamos mirar también, 2339 | 2340 | - [Self-referential functions and the design of options] 2341 | - [Functional options for friendly APIs] 2342 | 2343 | [Self-referential functions and the design of options]: https://commandcenter.blogspot.com/2014/01/self-referential-functions-and-design.html 2344 | [Functional options for friendly APIs]: https://dave.cheney.net/2014/10/17/functional-options-for-friendly-apis 2345 | 2346 | 2348 | --------------------------------------------------------------------------------