Cuándo y por qué debe usar void (en lugar de, por ejemplo, bool / int)


30

De vez en cuando me encuentro con métodos en los que un desarrollador elige devolver algo que no es crítico para la función. Quiero decir, cuando veo el código, aparentemente funciona igual de bien que, voidy después de un momento de reflexión, pregunto "¿Por qué?" ¿Te suena familiar?

A veces estoy de acuerdo en que la mayoría de las veces es mejor devolver algo como a boolo int, en lugar de simplemente hacer un void. Sin embargo, no estoy seguro, en general, sobre los pros y los contras.

Dependiendo de la situación, devolver un mensaje intpuede hacer que la persona que llama sepa la cantidad de filas u objetos afectados por el método (por ejemplo, 5 registros guardados en MSSQL). Si un método como "InsertSomething" devuelve un valor booleano, puedo tener el método diseñado para devolverlo truesi tiene éxito, de lo contrario false. La persona que llama puede elegir actuar o no con esa información.

Por otra parte,

  • ¿Puede conducir a un propósito menos claro de una llamada al método? La codificación incorrecta a menudo me obliga a verificar el contenido del método. Si devuelve algo, le dice que el método es del tipo que tiene que hacer algo con el resultado devuelto.
  • Otro problema sería, si la implementación del método es desconocida para usted, ¿qué decidió devolver el desarrollador que no es una función crítica? Por supuesto que puedes comentarlo.
  • El valor de retorno debe procesarse, cuando el procesamiento podría finalizar en el paréntesis de cierre del método.
  • ¿Qué pasa debajo del capó? ¿Se obtuvo el método llamado falsedebido a un error arrojado? ¿O devolvió falso debido al resultado evaluado?

¿Cuales son tus experiencias con esto? ¿Cómo actuarías en esto?


1
Una función que devuelve void no es técnicamente una función en absoluto. Es solo un método / procedimiento. Devolver voidal menos le permite al desarrollador saber que el valor de retorno del método es intrascendente; realiza una acción, en lugar de calcular un valor.
KChaloux

Respuestas:


32

En el caso de un valor de retorno bool para indicar el éxito o el fracaso de un método, prefiero el Tryparadigma de prefijo utilizado en varios métodos .NET.

Por ejemplo, un void InsertRow()método podría generar una excepción si ya existe una fila con la misma clave. La pregunta es, ¿es razonable suponer que la persona que llama se asegura de que su fila sea única antes de llamar a InsertRow? Si la respuesta es no, entonces también proporcionaría un bool TryInsertRow()que devuelve falso si la fila ya existe. En otros casos, como los errores de conectividad de db, TryInsertRow aún podría lanzar una excepción, suponiendo que mantener la conectividad de db es responsabilidad de la persona que llama.


44
+1 por hacer la distinción entre lidiar con fallas esperadas e inesperadas : mi propia respuesta no enfatizó esto lo suficiente.
Péter Török

Absolutamente un +1 para tratar de inventar un principio para los métodos TryXX que aparentemente hacen que tanto el método try como el método main sean más fáciles de mantener y más fáciles de entender para su propósito.
Independiente

+1 Bien dicho. La clave para la mejor respuesta para esta pregunta es que a veces desea darle a la persona que llama la opción de un método asertivo que arroje una excepción. En estos casos, sin embargo, la excepción no debe ser detectada y manejada falsamente por el método asertivo. El punto es que la persona que llama se hace responsable. Por otro lado, un paradigma Try (o Monad) debería capturar y manejar excepciones, luego devolver un bool o una Enum descriptiva si se necesitan más detalles. Tenga en cuenta que la persona que llama espera que un Try / Monad NUNCA arroje una excepción. Es como un punto de entrada.
Suamere

Por lo tanto, dado que se espera que nunca se produzca una excepción de un Try / Monad, sin embargo, un bool no es lo suficientemente descriptivo para informar al llamante que una conexión db falló, o una solicitud http tiene uno de los muchos valores de retorno que son necesarios para actuar de manera diferente, puedes usar una Enum en lugar de un bool para tu Try / Monad.
Suamere

Si tiene TryInsertRow, devolviendo un bool, si, por ejemplo, hay más de una restricción única en la base de datos, ¿cómo sabe exactamente cuál fue el error y se lo comunica al usuario? ¿Debería usarse un tipo de retorno más rico que contenga el mensaje / código de error y el bool de éxito?
niico

28

En mi humilde opinión, devolver un "código de estado" proviene de tiempos históricos, antes de que las excepciones se volvieran comunes en lenguajes de nivel medio a superior, como C #. Hoy en día es mejor lanzar una excepción si algún error inesperado impidió que su método tenga éxito. De esa manera, es seguro que el error no pasa desapercibido y la persona que llama puede tratarlo según corresponda. Entonces, en estos casos, está perfectamente bien que un método no devuelva nada (es decir void) en lugar de un indicador de estado booleano o un código de estado entero. (Por supuesto, uno debe documentar qué excepciones puede arrojar el método y cuándo).

Por otro lado, si es una función en sentido estricto, es decir, realiza algunos cálculos basados ​​en parámetros de entrada y se espera que devuelva el resultado de ese cálculo, el tipo de retorno es obvio.

Hay un área gris entre los dos, cuando uno puede decidir devolver alguna información "adicional" del método, si se considera útil para sus llamantes. Como su ejemplo sobre el número de filas afectadas en una inserción. O para un método para colocar un elemento en una colección asociativa, puede ser útil devolver el valor anterior asociado con esa clave, si hubiera alguna. Dichos usos pueden identificarse analizando cuidadosamente los escenarios de uso (conocidos y anticipados) de una API.


44
+1 para excepciones sobre el código de estado. La excepción (mal juego de palabras) es el patrón TryXX bien utilizado.
Andy Lowry

éter estoy contigo allí. TcF y no silenciar errores! Probablemente, sin embargo, la zona gris. No puede siempre ser de utilidad retorno de algo. Las filas afectadas, la última ID insertada, un PID de ejecución de software, pero aún así creo que es más fácil pensar "demonios, sí, lo devuelvo" cuando se desarrolla. Luego, un año después, al extender, "¿qué? Esto hace un'int someThing = RunProcess (x) '¿qué sucede si no se puede ejecutar?"
Independiente

La información adicional de +1 es la razón por la que codifico métodos como este en lenguajes de nivel superior como C #. Esto facilita el uso de la información adicional cuando la necesita.
cenizas999

2
-1 Wow, tus propias palabras son tan contradictorias e incorrectas. Nunca debe usar excepciones para el flujo de control o la comunicación. Son infinitamente más caros que un condicional. ¿Cuándo es el tiempo "antes de que las excepciones se vuelvan comunes"? ... ¿quiere decir antes de que los bloques try / catch se hayan convertido en comunes? Las excepciones han sido comunes desde siempre. "Lanzar una excepción si se produce un error inesperado ..." ¿Cómo se actúa sobre algo inesperado? ¿Por qué atraparías una excepción y luego la volverías a lanzar? Eso es muy caro. ¿Por qué dices "error"? Los errores y las excepciones son cosas diferentes.
Suamere

1
¿Y quién puede decir que una excepción lanzada no pasará desapercibida? Nada obliga a nadie a iniciar sesión en un bloque catch, ¿por qué no iniciar sesión en un bloque "then"? ¿Por qué "documentar qué excepciones puede lanzar el método y cuándo"? ¿Qué tal si escribimos código autodocumentado y un método simplemente devuelve una enumeración con los estados finales esperados de ese método? Si se puede evitar una posible excepción al verificar el estado, deben capturarse y manejarse sin lanzarse.
Suamere

14

La respuesta de Peter cubre bien las excepciones. Otra consideración aquí implica la separación de consulta de comando

El principio CQS dice que un método debería ser un comando o una consulta y no ambos. Los comandos nunca deben devolver un valor, solo modifican el estado y las consultas solo deben devolver y no modificar el estado. Esto mantiene la semántica muy clara y ayuda a que el código sea más legible y mantenible.

Hay algunos casos donde violar el principio CQS es una buena idea. Estos generalmente están relacionados con el rendimiento o la seguridad de los hilos.


1
-1 Incorrecto e incorrecto. En primer lugar, todo el punto de CQS radica en la Q. Una persona que llama debería poder obtener cálculos o llamadas externas a través de algún servicio con estado sin temor a alterar el estado de ese servicio. Además, si bien CQS es un principio útil que se debe seguir libremente, solo se sigue estrictamente en diseños específicos. Por último, incluso en CQS estricto, nada dice que un comando no pueda devolver un valor, dice que no debería estar calculando o llamando cálculos externos y devolviendo datos . C o Q pueden sentirse libres de devolver su estado final, si eso es útil para la persona que llama.
Suamere

Ah, pero el número de filas afectadas por un Comando hace que construir nuevos comandos a partir de los antiguos sea mucho más fácil. Fuente: años y años de experiencia.
Joshua

@Joshua: Del mismo modo, "agregar un nuevo registro y devolver una ID (para algunas estructuras, un número de fila) que se generó durante la adición" se puede utilizar para fines que de otro modo no se podrían lograr de manera eficiente.
supercat

No debería necesitar devolver las filas afectadas. El comando debe saber cuántos se verán afectados. Si es cualquier cosa menos ese #, debería retroceder y lanzar una excepción ya que algo extraño acaba de suceder. Por lo tanto, esa es información que a la persona que llama realmente no le importa (a menos que el usuario necesite saber el verdadero # y no se pueda distinguir de los datos proporcionados). Todavía hay áreas grises como ID generados por DB, pilas / colas donde obtener los datos cambia su estado, pero me gusta crear un "CommandQuery" en esos escenarios para que nadie intente hacer algo "extraño".
Daniel Lorenz el

3

Cualquiera sea el camino que vayas a caminar con esto. No tiene valores de retorno para su uso futuro (por ejemplo, las funciones siempre devuelven verdaderas), esto es una pérdida de esfuerzo terrible y no hace que el código sea más claro de leer. Cuando tenga un valor de retorno, siempre debe usarlo, y para poder usarlo debe tener al menos 2 valores de retorno posibles.


+1 Esto no responde la pregunta, pero definitivamente es información correcta. Al tomar una decisión, la decisión debe basarse en una necesidad. Si las personas que llaman no encuentran útiles los datos devueltos, no se debe hacer. Y nadie encuentra uso en devolver el verdadero 100% del tiempo para "pruebas futuras". De acuerdo, si el "futuro" es como ... dentro de un par de días y hay una tarea para ello, esa es una historia diferente, jajaja.
Suamere

1

Solía ​​escribir muchas funciones vacías. Pero desde que empecé con el método de encadenar crack, comencé a devolver esto en lugar de anular; también podría dejar que alguien se aproveche de la devolución porque no puedes hacer sentadillas con vacío. Y si no quieren hacer nada con él, simplemente pueden ignorarlo.


1
Sin embargo, el encadenamiento de métodos puede hacer que la herencia sea engorrosa y difícil. No haría el encadenamiento de métodos a menos que tenga una buena razón para hacerlo, aparte de no querer "anular" sus métodos.
KallDrexx

Cierto. Pero la herencia puede ser engorrosa y difícil de usar.
Wyatt Barnett

En cuanto a un código desconocido, que cualquiera que sea el retorno (menciona la totalidad del objeto) presta mucha atención solo la parte interna de flujo y métodos exterior ..
Independiente

¿Supongo que no te metiste en Rubyalgún momento? Eso es lo que me introdujo al método de encadenamiento.
KChaloux

Una duda que tengo con el método de encadenamiento es que deja menos claro si una declaración como return Thing.Change1().Change2();espera cambiar Thingy devolver una referencia al original, o si se espera que devuelva una referencia a algo nuevo que difiere del original, mientras deja el original sin tocar.
supercat
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.