¿Debería favorecerse la coherencia sobre la convención de programación?

14

Al diseñar una clase, ¿debería favorecerse la consistencia en el comportamiento sobre la práctica de programación común? Para dar un ejemplo específico:

Una convención común es esta: si una clase posee un objeto (por ejemplo, lo creó), es responsable de limpiarlo una vez que está hecho. Un ejemplo específico sería en .NET que si su clase posee un IDisposableobjeto, debería disponerlo al final de su vida útil. Y si no lo tienes, no lo toques.

Ahora, si miramos la StreamWriterclase en .NET, podemos encontrar en la documentación que cierra la secuencia subyacente cuando se cierra / elimina. Esto es necesario en los casos en que StreamWriterse instancia al pasar un nombre de archivo cuando el escritor crea la secuencia de archivos subyacente y, por lo tanto, debe cerrarla. Sin embargo, también se puede pasar una secuencia externa que el escritor también cierra.

Esto me ha molestado un montón de veces (sí, sé que puedes hacer un contenedor sin cierre, pero ese no es el punto), pero aparentemente Microsoft ha tomado la decisión de que es más consistente cerrar siempre la transmisión sin importar de dónde venga.

Cuando me encuentro con este patrón en una de mis clases, generalmente creo un ownsFooBarindicador que se establece en falso en los casos en que FooBarse inyecta a través del constructor y en verdadero de lo contrario. De esta forma, la responsabilidad de limpiarlo se transfiere a la persona que llama cuando pasa la instancia explícitamente.

Ahora me pregunto si tal vez la coherencia debería estar a favor de las mejores prácticas (o tal vez mi mejor práctica no es tan buena). ¿Algún argumento a favor o en contra?

Editar para aclaraciones

Con "consistencia" quiero decir: el comportamiento consistente de la clase siempre tomando posesión (y cerrando la transmisión) frente a "mejores prácticas" para tomar posesión de un objeto solo si lo creó o transfirió explícitamente la propiedad.

En cuanto a un ejemplo donde está enoying:

Suponga que tiene dos clases dadas (de una biblioteca de terceros) que aceptan una secuencia para hacer algo con ella, como crear y procesar algunos datos:

 public class DataProcessor
 {
     public Result ProcessData(Stream input)
     {
          using (var reader = new StreamReader(input))
          {
              ...
          }
     }
 }

 public class DataSource
 {
     public void GetData(Stream output)
     {
          using (var writer = new StreamWriter(output))
          {
               ....
          }
     }
 }

Ahora quiero usarlo así:

 Result ProcessSomething(DataSource source)
 {
      var processor = new DataProcessor();
      ...
      var ms = new MemoryStream();
      source.GetData(ms);
      return processor.ProcessData(ms);
 }

Esto fallará con una excepción Cannot access a closed streamen el procesador de datos. Está un poco construido pero debería ilustrar el punto. Hay varias formas de solucionarlo, pero sin embargo siento que trabajo alrededor de algo que no debería tener que hacerlo.

ChrisWue
fuente
¿Le importaría desarrollar POR QUÉ el comportamiento ilustrado de StreamWriter le causa dolor? ¿Te gustaría consumir la misma corriente en otro lugar? ¿Por qué?
Trabajo
@ Job: he aclarado mi pregunta
ChrisWue

Respuestas:

9

También utilizo esta técnica, la llamo 'traspaso' y creo que merece el estado de un patrón. Cuando un objeto A acepta un objeto desechable B como parámetro de tiempo de construcción, también acepta un booleano llamado 'traspaso' (que por defecto es falso) y, si es verdadero, la disposición de A cae en cascada para la disposición de B.

No estoy a favor de la proliferación de las decisiones desafortunadas de otras personas, por lo que nunca aceptaría la mala práctica de Microsoft como una convención establecida, ni consideraría el seguimiento ciego de otras personas de las decisiones desafortunadas de Microsoft como "comportamiento consistente" de ninguna manera .

Mike Nakis
fuente
Nota: Escribí una definición formal de este patrón en una publicación en mi blog: michael.gr: El patrón "Handoff" .
Mike Nakis
Como una extensión adicional del handOffpatrón, si dicha propiedad se establece en el constructor, pero existe otro método para modificarlo, ese otro método también debería aceptar una handOffbandera. Si handOffse especificó para el elemento actualmente retenido, debe eliminarse, entonces el nuevo elemento debe aceptarse y la handOffbandera interna debe actualizarse para reflejar el estado del nuevo elemento. El método no debería necesitar verificar las referencias antiguas y nuevas para la igualdad, ya que si la referencia original fue "entregada", todas las referencias en poder del llamador original deberían ser abandonadas.
supercat
@supercat Estoy de acuerdo, pero tengo un problema con la última oración: si el traspaso es verdadero para el artículo actualmente retenido, pero el artículo recién suministrado es igual en referencia al artículo actualmente retenido, y luego desechar el artículo actualmente retenido está en efecto eliminando el artículo recién suministrado. Creo que volver a suministrar el mismo artículo debería ser ilegal.
Mike Nakis
El reabastecimiento del mismo artículo generalmente debería ser ilegal a menos que el objeto sea inmutable, en realidad no contenga ningún recurso y no le importe si se elimina. Por ejemplo, si Disposese ignoraran las solicitudes de pinceles del sistema, un método "color.CreateBrush" podría crear un nuevo pincel (que requeriría eliminación) o devolver un pincel del sistema (que ignoraría la eliminación). La forma más sencilla de evitar la reutilización de un objeto si se preocupa por la eliminación, pero permiten la reutilización si no lo hace, es de deshacerse de él.
supercat
3

Creo que tienes razón, para ser honesto. Creo que Microsoft se equivocó con la clase StreamWriter, específicamente, por las razones que usted describe.

Sin embargo, desde entonces he visto una gran cantidad de código en el que las personas ni siquiera intentan deshacerse de su propio Stream porque el marco lo hará por ellos, por lo que arreglarlo ahora hará más daño que bien.

pdr
fuente
2

Yo iría con consistencia. Como mencionó a la mayoría de las personas, tiene sentido que si su objeto crea un objeto hijo, lo poseerá y lo destruirá. Si se le da una referencia desde el exterior, en algún momento "el exterior" también tiene el mismo objeto y no debería ser de su propiedad. Si desea transferir la propiedad desde el exterior a su objeto, utilice los métodos Adjuntar / Separar o un constructor que acepte un valor booleano que deje en claro que la propiedad se transfiere.

Después de escribir todo eso para obtener una respuesta completa, creo que la mayoría de los patrones de diseño genéricos no necesariamente caen en la misma categoría que las clases StreamWriter / StreamReader. Siempre he pensado que la razón por la que MS eligió que StreamWriter / Reader se hiciera cargo automáticamente de la propiedad es porque, en este caso específico, tener una propiedad compartida no tiene mucho sentido. No puede hacer que dos objetos diferentes lean / escriban simultáneamente en la misma secuencia. Estoy seguro de que podrías escribir algún tipo de tiempo compartido determinista, pero eso sería un anti-patrón infernal. Entonces, probablemente es por eso que dijeron, ya que no tiene sentido compartir una transmisión, simplemente tomemos el control. Pero no consideraría que este comportamiento se haya convertido en una convención genérica en todos los ámbitos para todas las clases.

Podría considerar que Streams es un patrón consistente en el que el objeto que se está pasando no es compartible desde el punto de vista del diseño y, por lo tanto, sin ningún indicador adicional, el lector / escritor simplemente se hace cargo de su propiedad.

DXM
fuente
Aclaré mi pregunta: con coherencia me refería al comportamiento de siempre tomar posesión.
ChrisWue
2

Ten cuidado. Lo que usted considera "consistencia" podría ser una colección aleatoria de hábitos.

"Coordinación de interfaces de usuario para la coherencia", Boletín SIGCHI 20, (1989), 63-65. Lo sentimos, no hay enlace, es un artículo viejo. "... un taller de dos días de 15 expertos no pudo producir una definición de consistencia". Sí, se trata de "interfaz", pero creo que si piensa en la "consistencia" por un tiempo, encontrará que tampoco puede definirla.

Bruce Ediger
fuente
tiene razón, si los expertos se unen de diferentes círculos, pero al desarrollar código, me resultó fácil seguir algún patrón existente (o hábito si lo desea) con el que mi equipo ya está familiarizado. Por ejemplo, el otro día uno de nuestros muchachos estaba preguntando acerca de algunas operaciones asincrónicas que tuvimos y cuando le dije que se comportan de la misma manera que Win32 Overlapped I / O, en 30 segundos entendió la interfaz completa ... en este caso, ambos Yo y él venimos del mismo servidor de fondo Win32. Consistencia por cierto! :)
DXM
@Bruce Entiendo tu punto: pensé que estaba claro a qué me refería con coherencia, pero aparentemente no fue así.
ChrisWue