La última vez escribí que los nombres de los objetos son de gran importancia y que deben seleccionarse cuidadosamente y con atención a los detalles. El mal nombre asusta y no permite comprender la esencia de lo que está sucediendo. Pero, ¿cuál es la esencia de esto?
Es difícil evaluar a un héroe sin comprender sus "estadísticas" y "habilidades" . Lo que puede y de lo que es capaz es el siguiente nivel de dificultad en el que tenemos que sumergirnos. No es suficiente reflejar el santuario interno del objeto con la ayuda de un nombre exacto, también debe asegurarse de que este sea el mismo santuario y no los establos de los captadores.
Sobre esto - en el artículo.
Tabla de contenidos del ciclo
- Los objetos
- Acciones y propiedades
- Código como texto
Acciones
El personaje ataca, defiende, esquiva, dispara desde un arco, usa hechizos y agita una espada. El nombre refleja el objeto, pero el objeto mismo está en movimiento, en reacción, en acción. De lo contrario, hablaríamos de tablas en Excel.
En C #, las acciones son métodos y funciones. Y para nosotros: verbos, átomos de movimiento verbal. Los verbos mueven el tiempo, debido a que los objetos existen e interactúan. Donde hay un cambio, debe haber un verbo.
Setters
De todos los cambios, la asignación es menos móvil. Describe estricta y matemáticamente qué cantidades son y a qué son iguales, pero nunca comunica vida y vigor a un texto, como lo hacen los verbos.
Por ejemplo, hay IPullRequest
con la propiedad Status
, que se puede IPullRequest
, IPullRequest
o Merged
. Puede escribir pullRequest.Status = Status.Declined
, pero esto es lo mismo que decir "Establecer la solicitud de grupo al estado cancelado", es imprescindible. Mucho más fuerte es pullRequest.Decline()
y, en consecuencia, pullRequest.Approve()
, pullRequest.Merge()
.
Un verbo activo es preferible a un setter, pero no todos los verbos lo son.
Voz pasiva
PerformPurchase
, DoDelete
, MakeCall
.
Al igual que en HeroManager
un sustantivo importante está oculto por un Manager
sin sentido, así que en PerformMigration
- Perform
. Después de todo, más vivo, ¡solo Migrate
!
Los verbos activos actualizan el texto: no "hit" , sino "hit" ; no "hizo un columpio" , sino "agitó" ; no "tomó una decisión" , sino "decidió" . Entonces, en el código: PerformApplication
→ Apply
; DoDelete
→ Delete
; PerformPurchase
→ Purchase
, Buy
. (Pero DealDamage
establecido, aunque en casos excepcionales se puede decir Attack
).
Evitando la voz pasiva, desarrollamos la historia, movemos a los personajes, pero también debemos asegurarnos de que la película no se vea en blanco y negro.
Verbos fuertes
Algunas palabras transmiten matices de significado mejor que otras. Si escribe "bebió un vaso de agua" , será simple y claro. Pero "drenó un vaso de agua" - en sentido figurado, más fuerte.
Por lo tanto, el cambio de salud del jugador se puede expresar a través de player.Health = X
o player.SetHealth
, pero más pintoresco es player.RestoreHealth
.
O, por ejemplo, sabemos Stack
no por Add/Remove
, sino por Push/Pop
.
Los verbos fuertes y activos saturan el objeto con comportamiento, si no son demasiado específicos.
Partes redundantes
Al igual que con ManualResetEvent
, cuanto más nos acercamos a los aspectos técnicos internos de .NET, que son complejos y sería bueno simplemente expresarlos, más ricos serán los detalles y los excesos de la API.
Sucede que necesita hacer algún trabajo en otro hilo, pero para no molestarse en crearlo y detenerlo. En C # hay ThreadPool
para esto. Solo aquí hay un simple "hacer el trabajo" aquí - QueueUserWorkItem
! Qué tipo de elemento de trabajo ( WorkItem
) es y qué puede ser, si no es usuario ( User
), no está claro. Mucho más fácil sería: ThreadPool.Run
o ThreadPool.Execute
.
Otro ejemplo. Recordar y saber que hay una instrucción atómica de comparar e intercambiar (CAS) es bueno, pero portarlo limpio al código no es la mejor solución. Interlocked.CompareExchange(ref x, newX, oldX)
es inferior a Atomically.Change(ref x, from: oldX, to: newX)
(usando parámetros con nombre).
El código no es un doctorado en el trabajo con una computadora cuántica, no es una aplicación para cálculos matemáticos, pero el lector a veces es completamente indiferente a lo que se llaman las instrucciones de bajo nivel. El uso diario es importante.
Repeticiones
UsersRepository.AddUser
, Benchmark.ExecuteBenchmark
, AppInitializer.Initialize
, UniversalMarshaller.Marshal
, Logger.LogError
.
Como dije en la última parte, la repetición erosiona el significado, comprime el espacio.
No UsersRepository.AddUser
, sino UsersRepository.Add
; no Directory.CreateDirectory
, sino Directory.Create
; no HttpWebResponse.GetResponseStream
, sino HttpWebResponse.Stream
; no Logger.LogError
, sino Log.Error
.
Arena fina
Check
es una palabra de muchos lados. CheckHasLongName
puede devolver un bool
o lanzar una excepción si el usuario tiene un nombre demasiado largo. Mejor es bool HasLongName
o void EnsureHasShortName
. Incluso conocí CheckRebootCounter
, que ... En algún lugar dentro, ¡reinicié IIS!
Enumerate
: de la misma serie. En .NET hay un método Directory.EnumerateDirectories(path)
: por alguna razón, se especifica que las carpetas se enumerarán, aunque Directories.Of(path)
o path.Directories()
.
Calc
: Calculate
se reduce a menudo, aunque se parece más a los depósitos de calcio.
Proc
es otra abreviatura elegante para Process
.
Base
, Impl
, Internal
, Raw
: palabras parásitas que indican la complejidad de los objetos.
Total
Una vez más, un lector atento se dará cuenta, todo se reduce a la simplificación, a comparar el habla natural, y los consejos en sí mismos se relacionan en gran medida no solo con el código, sino con la escritura en general. Utilizándolos, el desarrollador pule tanto el código como el texto y el texto mismo, buscando una presentación transparente y fluida, para simplificar.
Ahora que hemos descubierto el movimiento y los "efectos especiales", veamos cómo se describen las relaciones entre los objetos.
Las propiedades
El personaje tiene salud y maná; los artículos están en la cesta de la compra; El sistema solar está formado por planetas. Los objetos no solo actúan desinteresadamente, sino que también se relacionan: jerárquicamente (antepasado-heredero), composicional (parte completa), espacialmente (elemento de almacenamiento), etc.
En C #, las propiedades y las relaciones son métodos (generalmente comenzando con Get
), getters (propiedades con un cuerpo get
específico) y campos. Pero para nosotros es: adiciones de palabras que expresan la pertenencia de un objeto a otro. Por ejemplo, un jugador tiene salud - Player.Health
, que corresponde casi exactamente a la "salud del jugador" en inglés .
Lo que más se confunde hoy son los métodos de acción y los métodos de propiedad.
Verbo en lugar de un sustantivo
GetDiscount
, CalculateDamage
, FetchResult
, ComputeFov
, CreateMap
.
Los asentamientos se escuchan en todas partes: los métodos deben comenzar con verbos. Raramente ves a alguien dudando: ¿es realmente así? Después de todo, no puede haber una diferencia significativa entre Player.Health
y Player.Health()
. Deje que los registros sean sintácticamente diferentes, significan lo mismo.
Supongamos que en IUsersRepository
se espera fácilmente un GetUser(int id)
. ¿Por qué, para representar al usuario, piensa en algún tipo de recibo ( Get
)? Será más preciso: ¡ User(int id)
!
Y realmente: no FetchResult()
, sino Result()
; no GetResponse()
, sino Response()
; no CalculateDamage()
, pero Damage()
.
Una charla DDD da un ejemplo de un código "bueno": DiscountCalculator
con el método CalculateDiscountBy(int customerId)
. No solo hay una repetición simétrica en la cara: DiscountCalculator.CalculateDiscount
, también especificaron que se calcula el descuento. ¿Y qué más, uno pregunta, que hacer con ella?
Sería más fuerte pasar de la entidad misma: el Discount
con el método static decimal Of(Customer customer, Order order)
para llamar a Discount.Of(customer, order)
es más simple que _discountCalculator.CalculateDiscountBy(customerId)
y corresponde a un solo idioma.
A veces, al omitir el verbo, perdemos algo, como, por ejemplo, en CreateMap()
: un reemplazo directo con Map()
puede no ser suficiente. Entonces la mejor solución es NewMap()
: nuevamente, el objeto está en la cabeza, no la acción.
El uso de verbos vacíos vacíos es característico de una cultura obsoleta e imperativa, donde el algoritmo es primario y está por delante del concepto. Allí a menudo encontrará una "cuchilla que ha sido templada" que una "cuchilla endurecida" . Pero el estilo de los libros sobre James Bond no es adecuado para describir el paisaje. Donde no hay movimiento, no hay lugar para el verbo.
Otros
Las propiedades y métodos que expresan las relaciones entre los objetos también son objetos, por lo tanto, lo mencionado anteriormente en muchos aspectos se aplica a ellos.
Por ejemplo, repeticiones en propiedades: no Thread.CurrentThread
, sino Thread.Current
; no Inventory.InventoryItems
, sino Inventory.Items
, etc.
Total
Las palabras simples y comprensibles no se confunden y, por lo tanto, el código que las contiene tampoco se confunde. En la escritura, es igualmente importante escribir fácilmente: evitar preposiciones pasivas, una gran cantidad de adverbios y adjetivos, repeticiones, porque las acciones prefieren un verbo a un sustantivo. Un ejemplo bien conocido: "Asintió con la cabeza, de acuerdo", en lugar de "Asintió", provoca una sonrisa, y recuerdo QueueUserWorkItem
.
El texto del código también es diferente en que en el primer caso se le pagará si la casa está de pie, ahogada por los rayos del sol poniente ; en el segundo, si la casa está en pie ; pero vale la pena recordar: una casa debe estar de pie, y no palos de los ayudantes.
En los primeros dos artículos de la serie, quería mostrar lo importante que es trabajar no solo en el algoritmo, sino también en la palabra; cómo los nombres determinan el contenido de lo que se llama; cómo el código redundante y demasiado complicado aleja al lector.
Junto con esto, los buenos nombres son solo notas. Para jugar , deben estar escritos y encarnados en la música. Te contaré más en el próximo artículo final.