Práctica recomendada: ajuste en función de la llamada a la función versus adición de salida anticipada en caso de protección en la función

9

Sé que esto puede ser muy específico para el caso de uso, pero me pregunto con demasiada frecuencia. ¿Existe una sintaxis generalmente preferida?

No estoy preguntando cuál es el mejor enfoque cuando estoy en una función, estoy preguntando si debo salir temprano o simplemente no llamar a la función.

Ajustar si alrededor de la llamada de función


if (shouldThisRun) {
  runFunction();
}

Tener if ( guardia ) en función

runFunction() {
  if (!shouldThisRun) return;
}

La última opción obviamente tiene el potencial de reducir la duplicación de código si esta función se llama varias veces, pero a veces se siente mal agregarla aquí, ya que puede estar perdiendo la responsabilidad única de la función.


Heres un ejemplo

Si tengo una función updateStatus () que simplemente actualiza el estado de algo. Solo quiero que se actualice el estado si el estado ha cambiado. Sé los lugares en mi código donde el estado tiene el potencial de cambiar, y sé otros lugares donde definitivamente ha cambiado.

No estoy seguro de si soy solo yo, pero me parece un poco sucio comprobar esta función interna, ya que quiero mantener esta función lo más pura posible; si la llamo, espero que se actualice el estado. Pero no puedo decir si es mejor terminar la llamada en un cheque en los pocos lugares donde sé que tiene el potencial de no haber cambiado.

Matthew Mullin
fuente
3
@gnat No, esa pregunta es esencialmente '¿Cuál es la sintaxis preferida en una salida anticipada' mientras que la mía es '¿Debería salir temprano o simplemente no debería llamar a la función?'
Matthew Mullin
44
no puede confiar en los desarrolladores, ni siquiera en usted , para verificar correctamente las condiciones previas de la función en todas partes donde se llama. Por esta razón, sugeriría que la función valide internamente cualquier condición necesaria si tiene la capacidad de hacerlo.
TZHX
"Solo quiero que se actualice el estado si el estado ha cambiado". ¿Desea que se actualice un estado (= cambiado) si el mismo estado ha cambiado? Suena bastante circular. ¿Puede aclarar qué quiere decir con esto, para que pueda agregar un ejemplo significativo a mi respuesta sobre esto?
Doc Brown
@DocBrown Permite, por ejemplo, decir que quiero mantener sincronizadas dos propiedades de estado de objetos diferentes. Cuando cualquiera de los objetos cambia, llamo syncStatuses (), pero esto podría activarse para muchos cambios de campo diferentes (no solo el campo de estado).
Matthew Mullin

Respuestas:

15

Ajustar un if alrededor de una llamada de función:
Esto es para decidir si la función debe llamarse en absoluto, y es parte del proceso de toma de decisiones de su programa.

Cláusula
de protección en función (retorno temprano): Esto es para proteger contra ser llamado con parámetros inválidos

Una cláusula de protección utilizada de esta manera mantiene la función "pura" (su término). Solo existe para garantizar que la función no se rompa con datos de entrada erróneos.

La lógica sobre si llamar a la función es un nivel más alto de abstracción, incluso si solo es así. Debería existir fuera de la función misma. Como dice DocBrown, puede tener una función intermedia que realice esta verificación, para simplificar el código.

Esta es una buena pregunta, y se enmarca dentro del conjunto de preguntas que conducen al reconocimiento de los niveles de abstracción. Cada función debe operar en un solo nivel de abstracción, y tener tanto la lógica del programa como la lógica de la función en la función se siente mal para usted, esto se debe a que están en diferentes niveles de abstracción. La función en sí es un nivel inferior.

Al mantenerlos separados, se asegura de que su código sea más fácil de escribir, leer y mantener.

Baldrickk
fuente
Impresionante respuesta. Me gusta el hecho de que me da una forma clara de pensar sobre esto. El if debería estar fuera ya que es 'parte del proceso de toma de decisiones' en cuanto a si la función debe llamarse en primer lugar. E inherentemente no tiene nada que ver con la función en sí. Se siente extraño marcar una respuesta de opinión como correcta, pero volveré a mirar en unas horas y lo haré.
Matthew Mullin
Si ayuda, no considero esto como una respuesta de "opinión". Noto que "se siente" mal, pero eso se debe a que los diferentes niveles de abstracción no están separados. Lo que obtuve de su pregunta es que puede ver que no es "correcto", pero debido a que no está pensando en los niveles de abstracción, es difícil de cuantificar, por lo tanto, es algo que le cuesta poner en palabras.
Baldrickk
7

Puede tener ambas: una función que no verifica los parámetros y otra que sí, de esta manera (tal vez devolviendo información sobre si se realizó la llamada):

bool tryRunFunction(...)
{
    bool shouldThisRun = /* some logic using data not available inside "runFunction"*/;
    if (shouldThisRun)
        runFunction();
    return shouldThisRun;
}

De esa manera, puede evitar la lógica duplicada al proporcionar una función reutilizable tryRunFunctiony aún tener su función original (tal vez pura) que no realiza la verificación en el interior.

Tenga en cuenta que a veces necesitará una función como tryRunFunctioncon un cheque integrado exclusivamente, por lo que puede integrar el cheque en runFunction. O no tiene necesidad de volver a usar el cheque en ningún lugar de su programa, en cuyo caso puede dejarlo en la función de llamada.

Sin embargo, trate de hacer que sea transparente para la persona que llama lo que sucede dando a sus funciones nombres propios. Por lo tanto, las personas que llaman no tienen que adivinar o analizar la implementación si tienen que hacer las comprobaciones por sí mismos, o si la función llamada ya lo hace por ellos. Un prefijo simple como a trymenudo puede ser suficiente para esto.

Doc Brown
fuente
1
Tengo que admitir que el idioma "tryXXX ()" siempre parecía un poco desagradable y es inapropiado aquí. No estás intentando hacer algo esperando un posible error. Estás actualizando si está sucio.
user949300
@ user949300: elegir un buen nombre o esquema de nomenclatura depende del caso de uso real, los nombres de las funciones reales, no algún nombre artificial como runFunction. Una función como updateStatus()puede ir acompañada de otra función como updateIfStatusHasChanged(). Pero esto es 100% dependiente de los casos, no hay una solución "única para todos", así que sí, estoy de acuerdo, el idioma "intentar" no siempre es una buena opción.
Doc Brown
¿No hay algo llamado "dryRun"? Más o menos llega a ser una ejecución regular sin efectos secundarios. Cómo deshabilitar los efectos secundarios es otra historia
Laiv
3

En cuanto a quién decide si ejecutar, la respuesta es, de GRASP , quién es el "experto en información" que sabe.

Una vez que haya decidido eso, considere cambiar el nombre de la función para mayor claridad.

Algo como esto, si la función decide:

 ensureUpdated()
 updateIfDirty()

O, si se supone que la persona que llama debe decidir:

 writeStatus()
user949300
fuente
2

Me gustaría ampliar la respuesta de @ Baldrickk.

No hay una respuesta general a su pregunta. Depende del significado (contrato) de la función a llamar y la naturaleza de la condición.

Analicemoslo en el contexto de su llamada de ejemplo updateStatus(). Su contrato probablemente sea actualizar algún estado porque ha sucedido algo que influye en el estado. Esperaría que se permitan las llamadas a ese método incluso si no hay un cambio de estado real, y que sea necesario si hay un cambio real.

Por lo tanto, un sitio de llamadas puede omitir una llamada updateStatus()si sabe que (dentro de su horizonte de dominio) nada relevante ha cambiado. Esas son las situaciones en las que la llamada debe estar rodeada por una ifconstrucción apropiada .

Dentro de la updateStatus()función, puede haber situaciones en las que esta función detecta (a partir de datos dentro de su horizonte de dominio) que no hay nada que hacer, y ahí es donde debería regresar antes.

Entonces, las preguntas son:

  • Desde el punto de vista externo, ¿cuándo se permite / requiere llamar a la función, teniendo en cuenta el contrato de la función?
  • Dentro de la función, ¿hay situaciones en las que pueda regresar temprano sin ningún trabajo real?
  • ¿La condición de si se debe llamar a la función / si debe regresar antes pertenece al dominio interno de la función o al exterior?

Con una updateStatus()función, esperaría ver ambas cosas: llamar a sitios que no saben que nada ha cambiado, omitir la llamada y verificar la implementación de las situaciones de "nada cambiado" antes, incluso si de esta forma la misma condición se verifica dos veces, ambas dentro y fuera.

Ralf Kleberhoff
fuente
2

Hay muchas buenas explicaciones. Pero quiero parecer inusual: suponga que lo usa de esta manera:

if (shouldThisRun) {
   runFunction();
}

runFunction() {
   if (!shouldThisRun) return;
}

Y necesita llamar a otra función en un runFunctionmétodo como este:

runFunction() {
   if (!shouldThisRun) return;
   someOtherfunction();
}

¿Qué harás? ¿Copias todas las validaciones de arriba a abajo?

someOtherfunction() {
   if (!shouldThisRun) return;
}

No lo creo. Por lo tanto, generalmente hago el mismo enfoque: validar entradas y verificar condiciones en el publicmétodo. Los métodos públicos deben hacer sus propias validaciones y verificar las condiciones requeridas, incluso las personas que llaman lo hacen. Pero deje que los métodos privados solo hagan su propio negocio . Alguna otra función puede llamar runFunctionsin hacer ninguna validación o verificar ninguna condición.

public someFunction() {
   if (shouldThisRun) {
      runFunction();
   }
}

private runFunction() {
 // do your business.
}
Engineert
fuente