PDA

Ver la Versión Completa : Una correcta manera de utilizar TFileStream capturando posibles excepciones variadas?


Delphius
27-05-2015, 19:21:36
Pues, eso... como dice el título. ¿Cuál es la manera más apropiada de manejar un TFileStream capturando y lanzando las excepciones de forma apropiada?

Actualmente tengo esta chapuza de código:



procedure TArrayConverter.LoadMatrix(AMatrix: TAMatrix; FileName: string);
var Header: TMatrixHeader;
Fmt: TFileFormat;
i, j, RM, CM, Err, ConvE: integer;
Can: boolean;
begin
Can := CheckMatrix(AMatrix, RM, CM, Err);
if Can AND (Err = OPERATION_DONE)
then begin
try
// Cargar el archivo
FFile.Create(FileName, fmOpenRead or fmShareDenyWrite);
// leemos formato
FInUse := true;
Fmt.SizeFile := FFile.Seek(0, soEnd);
FFile.ReadBuffer(Fmt.IDIni, SizeOf(Fmt.IDIni)); // ID.Ini
FFile.ReadBuffer(Header, SizeOf(Header)); // Header
FFile.Seek(INI_ID_END, soFromEnd);
FFile.ReadBuffer(Fmt.IDEnd, SizeOf(Fmt.IDEnd)); // ID.End
// ¿Valido?
if ISValidFormat(Fmt, Header)
then begin
// Supuesto tamaño de datos
if (Header.Rows = RM) AND (Header.Cols = CM)
then begin
// Leemos data
if Header.Orientation = aoCol
then begin
// ... por columnas
for j := 0 to CM - 1 do
for i := 0 to RM - 1 do
FFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
end
else begin
// ... por filas
for i := 0 to RM - 1 do
for j := 0 to CM - 1 do
FFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
end;
end
else ConvE := 3;
end
else ConvE := 2;
finally
FFile.Free;
FInUse := false;
end;
end
else ConvE := 1;
// ¿Lanzamos excepción?
case ConvE of
1: raise EInconsistentArray.Create('Check of matrix failed', itCheck);
2: raise EFileOperation.Create('Invalid matrix file format');
3: raise EInconsistentArray.Create('The dimensions of the matrix and file data do not match',
itDimNotMatch);
end;
end;




Creería que el código se explica solo. Básicamente estoy leyendo un archivo matricial y cargando la data. Mi archivo está pensado con una estructura como la siguiente:

1) Viene una especie de identificador de archivo. Para el caso de matrices, tiene un valor y para vectores otro.
2) Seguidamente una cabecera que contiene la información relacionada con la dimensión de la estructura de datos.
3) Los datos propiamente dichos
4) Y por último un identificador de fin de archivo. Este es común para ambos tipos de archivos.


Se que puede ser mejorado. Pero ya tengo la cabeza muy hecha trizas, y se me confunden los tantos... Por un lado quisiera poder detectar posibles excepciones que arroje el FFileStream, desde su creación hasta cuando intenta leer y/o escribir y por el otro quisiera poder lanzar las excepciones propias que tengo definidas para el contexto de esta clase que estoy diseñando en base a mis pruebas. ¿Cómo debo proceder? ¿Un doble try anidado? ¿Cómo lo encararían ustedes?

Por si ayuda a comprender el código, adjunto la descripción de la clase:



TArrayConverter = class
private
FFile: TFileStream;
FInUse: boolean;
function IsValidFormat(AFormat: TFileFormat; AHeader: TMatrixHeader): boolean; overload;
function ISValidFormat(AFormat: TFileFormat; AHeader: TVectorHeader): boolean; overload;
function MakeHeader(Rows, Cols: integer; Orientation: TArrayOrientation): TMatrixHeader;
overload;
function MakeHeader(Dim: integer): TVectorHeader; overload;
public
constructor Create;
destructor Destroy; override;
procedure LoadMatrix(AMatrix: TAMatrix; FileName: string);
procedure LoadVector(AVector: TAVector; FileName: string);
procedure SaveMatrix(AMatrix: TAMatrix; OnDir: TArrayOrientation; FileName: string);
procedure SaveVector(AVector: TAVector; FileName: string);
property InUse: boolean read FInUse;
end;



Los métodos MakeHeader() como IsValidFormat() creerían que no hace falta que adjunte. Se pueden hacer una idea de su uso. Básicamente se procede a armar la cabecera de cada archivo, y en los otros de realizar las comprobaciones de formato para asegurarse de que se ha leído un archivo correcto.

Leyendo la documentación, durante la creación del TFileStream es posible que se presente una excepción EFOpenError, y también puede presentarse excepciones durante el ReadBuffer y en el WriteBuffer... no dice la documentación cual. Aparentemente, de lo que estoy viendo en el código de la clase TStream parecen ser EReadError y EWriteError respectivamente.

Para el guardado de vectores el código sería algo similar. Las versiones Save() estimo que debieran de tener un código análogo, salvando el detalle de aplicar un WriteBuffer.

Cualquier ayuda y propuesta se les agradece.


De algunas búsquedas que he estado realizano he notado que todos los códigos de ejemplo no protegen la creación del TFileStream dentro de un try, si en cambio asumen un código exitoso y proceden a aplicar un try-finally para asi liberar el TFileStream. Es decir:


FS := TFileStream.Create(...);
try
// Usar el FS
finally
FS.Free;
end;


Naturalmente, cuando se puede dar garantías de que el modo de apertura o creación del archivo nos permite un código seguro para una lectura o bien de la escritura no hay problemas con el código anterior (o al menos no esperaría algo tan problemático). Sobre todo si uno tiene separado lo que hace escritura de la lectura.
Me he estado cuestionando, ¿Y si espero que mis archivos sean de lectura Y escritura? ¿Que acaso no vale la pena proteger la creación del TFileStream?


Yo ya estoy medio perdido.

Recuerdo la discusión debate (http://www.clubdelphi.com/foros/showthread.php?t=59241) que hubo en ClubDdelphi sobre algo relacionado con esto.

En él se comentó sobre el caso particular que plantea justamente: para el caso en donde un constructor eleva una excepción. Para ese entonces, el debate no hiló fino en ese caso y se abordó hacia un planteo general. La documentación de Delphi sugiere que un objeto es creado o no creado. Nada de a medias, que se invoca a Destroy implícitamente y no tendría sentido un Free por el lado de Finally.
Más me sigo cuestionando, ¿Y que hacemos como excepción?
Tu dices que debiera de controlarse efectivamente de que el recurso, en este caso, un archivo esté abierto.

Sabemos que entre Delphi y Lazarus tienen sus diferencias. ¿Aquí será un caso? Intenté llegar a algo que me aclare el punto, y llego a algo parecido (https://www.mail-archive.com/search?l=lazarus@lists.lazarus.freepascal.org&q=subject:%22%5BLazarus%5D+exception+handling%22&o=newest&f=1) a la discusión en CD pero para el caso de Lazarus, no hay una confirmación real si FPC se comporta igual que Delphi en esto. La gente que ha intervenido en el mailing list asume que SI.

¿Debiera de proteger con except? ¿con Finally? ¿emplear doble try como se ejemplifica acá (http://stackoverflow.com/questions/6601147/how-to-correctly-write-try-finally-except-statements)?

Yo ya estoy confundido. http://delphiaccess.com/foros/public/style_emoticons/default/sad.png

Suelo tener cuidado con los constructores y destructores. Me he valido varias veces del uso de excepciones, pero el emplear el TFileStream y el querer poder tanto capturar posibles excepciones de éste, como de lanzar algunas propias para el contexto de mi clase TConverter y que en última las clases que usen a ésta sepan valerse me ha vuelto a las bases para replantear estas cuestiones.


Muchas gracias.

Casimiro Notevi
27-05-2015, 20:26:43
Pues yo no sabría responder con seguridad tu duda, así que mejor no contesto porque no quiero liarte más, a ver si alguien experto en el tema es capaz de iluminarnos.

Lo que sí quiero es saludarte, que hacía tiempo que no te veía, espero que todo te vaya bien ^\||/

Delphius
27-05-2015, 22:33:16
Pues yo no sabría responder con seguridad tu duda, así que mejor no contesto porque no quiero no liarte más, a ver si alguien experto en el tema es capaz de iluminarnos.

Lo que sí quiero es saludarte, que hacía tiempo que no te veía, espero que todo te vaya bien ^\||/

Casimiro, me extraña de vos. Se que eres un maestro y sabes un montón. Asi que no me vengas con lo de hacerse de menos.
Cualquier aporte ayudará a este pobre loco.

Gracias por el saludo. Se agradece muchísimo. Lamentablemente debo informar que mi situación personal no es del todo buena. Estoy con un pié en la calle; prácticamente sin plata en mis bolsillos, y las oportunidades comerciales y profesionales no son muy alentadoras.
Tengo que confesarte que desde que empecé mis estudios universitarios quien debían en teoría debe dar apoyo lo único que ha hecho fue acortarme las posibilidades de desarrollarme profesionalmente y constantemente ha estado cerrando las puertas que yo por mis medios estuve tratando de abrilas. Las situación ya era tan insorportable que para evitar un mal mayor, tuve que abandonar mi lugar de residencia y buscar hospedaje en otros lugares. Ya debo volver en unos días y estoy seriamente convencido de iniciar acciones legales contra esta persona de no lograr que desaparezca y me deje tranquilo y al resto. Demasiado daño ha causado al círculo familiar. En realidad son 16 años de tortura, pero desde que cumplí los 18 se ha vuelto peor y no dejan de llegarme comentarios de los desmadres que ocasiona a terceros. Basta con decir que algunos de mis amigos de Facultad no han querido pisar más mi casa para no tener problemas con él; e incluso una psicóloga conocida me ha sugerido que lo deberíamos de internar bajo tratamiento psiquiátrico. Intenté que las cosas mejoraran por las buenas, y mucho me aguanté y tragué pero veo que tendrán que ser por las malas.
Actualmente estoy tratando de terminar unos proyectos, y quisiera ver si de una vez logro concretar que se cierre algunas ideas comerciales que vengo pensando y me han sugerido mis amigos que me quedan en contacto y tengo más a mano. Lamentablemente por el tipo de formación que he recibido y los gustos y aficciones a ciertos temas que por acá pocos manejan no hay mucho mercado. No queda otra que jugar a la Oferta para generar Demanda.

Bueno, mejor volvamos el hilo a su cauce normal.

Saludos,

mamcx
27-05-2015, 22:38:18
He estado aprendiendo mucho últimamente sobre programación funcional, en especial con F#.

Este articulo lo recomiendo muchísimo:

http://fsharpforfunandprofit.com/fppatterns/

Pongo esto porque conceptualmente aclara muchas cosas, lo que es mas "difícil" es aplicarlo a un lenguaje OO. Afortunadamente, Delphi es lo suficiente flexible y resulta mas simple que con Java!

Ahora voy a varios puntos:
---

he notado que todos los códigos de ejemplo no protegen la creación de

Lo cual es correcto, tanto conceptual como técnicamente. En vez de irme por las ramas, un concepto muy practico que viene del lenguaje funcional Erlang es:

"Fallar de inmediato"
https://www.sics.se/~joe/thesis/armstrong_thesis_2003.pdf

(Secciones 4.3 / 4.4).

En resumen? La forma correcta de hacer programas robustos es no hacer programación "primariamente" defensiva, sino permitir fallar rápido, y tener un "supervisor" que se encargue de aplicar una política de recuperación.


Error handling in Erlang is radically diferent to error handing in most
other programming languages. The Erlang philosophy for handling errors
can be expressed in a number of slogans:

• Let some other process do the error recovery.
• If you can’t do what you want to do, die.
• Let it crash.
• Do not program defensively.


Que es casi exactamente lo que sucede aquí. Si la creación del stream fracasa, NO TIENE SENTIDO SEGUIR ADELANTE. Es responsabilidad de su "supervisor" (ie: Quien llamo al método donde se intento crear el stream) determinar que hacer (reintentar? Abortar todo el sistema? Notificar y esperar un tiempo? etc).

Esto significa que solo te debes preocupar por resolver el problema a mano, no todos los posibles. Intentar aplicar una programación excesivamente defensiva es un anti-patron, que lleva a tonterías como:


try
//Alguna cosa
except:
//No hacer nada o suponer que el error fue de permisos insuficientes!
end;


Es curioso, porque el uso de excepciones es una forma simplista de aplicar lo que dicen en Erlang, el problema es que la gente no le pone cuidado a lo que lees. Como es? "EXCEPCIONES", solo, "EXCEPCIONAL!". Una vez manejado (si acaso) lo excepcional, debe andar en la "ruta feliz" donde se asume que todo ira bien.

Te preguntaras porque arranco por aquí. Es simplemente porque quiero que solo te preocupes por "resolver el problema que tienes a mano", lo cual, lleva a hacer funciones/métodos cortos y faciles de testear.

Esa es la tesis del creado de Erlang. Y Erlang es famoso por ser el lenguaje/runtime mas robusto de todos, asi que tienen de donde saber porque es mejor asi ;)

--

La segunda tesis importante que se aprende con programación funcional, es que el "manejo de estado" es una de las principales razones de la complejidad no esencial en el desarrollo de software. Eso se nota en tu código con el asunto de la variable ConvE.

La pregunta es: Como hago el código, que siga el modelo de la computacion I/O: Entrada -> Proceso -> Salida, con el minimo de dependencias y estado no esencial?

---

Ahora como lo aplicaria yo (no testee el codigo, solo lo reformatee), sin desviarme mucho del estilo que tienes:



function LoadFile(FileName: string):TFileStream;
begin
Result := FFile.Create(FileName, fmOpenRead or fmShareDenyWrite);
//FInUse := true; Redundante: El OS ya hace esto
//Hasta
FFile.ReadBuffer(Fmt.IDEnd, SizeOf(Fmt.IDEnd)); // ID.End
end;

procedure ReadMatrix(AMatrix: TAMatrix; FFile: TFileStream; RM, CM:Int);
begin
// Mover los problemas mas cerca de donde se detectan!
if not(ISValidFormat(Fmt, Header)) then
begin
raise EFileOperation.Create('Invalid matrix file format');
end;

if not((Header.Rows = RM) AND (Header.Cols = CM))then
begin
raise EInconsistentArray.Create('The dimensions of the matrix and file data do not match', itDimNotMatch);
end;

//Ahora vamos por el "Happy Path!"
// Leemos data
if Header.Orientation = aoCol
then begin
// ... por columnas
for j := 0 to CM - 1 do
for i := 0 to RM - 1 do
FFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
end
else begin
// ... por filas
for i := 0 to RM - 1 do
for j := 0 to CM - 1 do
FFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
end;
end
end;

procedure TArrayConverter.LoadMatrix(AMatrix: TAMatrix; FileName: string);
var Header: TMatrixHeader;
Fmt: TFileFormat;
i, j, RM, CM, Err, ConvE: integer;
Can: boolean;
begin
Can := CheckMatrix(AMatrix, RM, CM, Err);
if not(Can AND (Err = OPERATION_DONE)) then
begin
EInconsistentArray.Create('Check of matrix failed', itCheck)
end;

FFile = LoadFile(FileName);
try
ReadMatrix(AMatrix, FFile, RM, CM);
finally
FFile.Free;
end;
end;


Podras notar varias cosas:

1- Eliminar el nesting hacer mucho mas legible el código, mas de 3 niveles de identacion suele ser una mala señal

2- Poner cerca la detección de los problemas, hace mas claro el codigo (y concuerda con "fallar rápido" ie: poner primero fallar, luego el "happy path")

3- Es mejor programar con el "happy path" o la ruta feliz: Una vez que estoy en la ruta feliz, se asume que todo debe funcionar ok, a menos que algo realmente inesperado suceda (como quedarse sin memoria) donde la única opción sana, es matar el programa.

4- El uso de "FInUse" no le veo sentido: Si lo que quieres es decir que el archivo esta en uso, no es tu codigo, sino el OS, quien realmente sabe si es verdad y quien es el responsable.

Si lo que quieres es evitar que se carguen 2 veces el archivo, esa no es la manera robusta.

5- Separar en funciones es algo que me gusta porque simplifica la lectura, pero en este caso con la reduccion de la identacion y mover la deteccion quedaria igual de chulo.


Aun mas que se puede hacer, pero no quise hacer un cambio muy radical, y como hace un rato que estoy oxidado con Delphi y estoy muy metido con otros lenguajes no quise hacer algo muy alienigena ;)

Delphius
27-05-2015, 23:01:22
Gracias Mamx por colaborar.

Estuve pensando en un método tipo PrepareStream() que bien o regrese un valor boolean o una excepción para determinar si algo ha fallado pero no he probado esta opción.

Tengo que pensar un poco tu propuesta. Se ve interesante el enfoque.

Te comento el porqué de la propiedad InUse. La clase está pensada a modo singleton (aunque no hay un control estricto en ello, y nada que impida crear varias instancias) debido a que es posible invocarla desde más de 2 módulos/clases independientes. De este modo, desde cualquier parte interesada en invocarla sepa si está disponible. No vi estrictamente necesario hacerlo por la vía Observer ya que no espero demasiada cosa.

Saludos,

Casimiro Notevi
27-05-2015, 23:21:17
Creo recordar que en "La cara oculta de Delphi 4 (http://terawiki.clubdelphi.com/Delphi/Manuales/?download=La_Cara_Oculta_De_Delphi_4_pdf_.zip)" había un capítulo sobre el tema.

Vaya situación personal la tuya, a ver si se arregla pronto, ánimos :)

Delphius
27-05-2015, 23:54:32
Creo recordar que en "La cara oculta de Delphi 4 (http://terawiki.clubdelphi.com/Delphi/Manuales/?download=La_Cara_Oculta_De_Delphi_4_pdf_.zip)" había un capítulo sobre el tema.

Vaya situación personal la tuya, a ver si se arregla pronto, ánimos :)
Es cierto, hay un capítulo que trata la teoría de las excepciones, y habla de las reglas de oro de cómo y cuando usarlas. Pero el mismo al final aclara que incluso esas reglas están hechas para romperlas. Tengo que admitir que tengo oxidada esa parte. Y es justamente entre esas cosas, lo que extraño tratar de hacer memoria a como iba y por ello es que acudo a los foros, a ver quien me hecha luz.

El tiempo que estuve fuera de la programación si que ha oxidado mi cabeza :o

Lo de arreglar mis problemas, no creo que desaparezcan prontamente. Va a ser una batalla dura seguramente. Ni te imaginas cuanto deseo desapegarme de eso, pero lamentablemente tuve que estar bastante embarrado para evitar que otros, más indefensos que yo, salieran más lástimado y controlar a que la bestia le salte más de lo normal los tapones contra otros. Lo que esperaba de mi ausencia es poner en evidencia de que si no saltan más fuerte los tapones es porque justamente yo era quien venía frenando a esa bestia (algún "provecho" tenía que haberle sacado a lo poco que aprendí de arte marciales). Van a tener que aprender a defenderse solas, no es posible que deba dejar mi vida para evitar que las golpeen y tener que tener un ojo siempre despierto vigilando a ver que trama el loco. Bastante he salido a defendarlas cada vez que piden auxilio. Lamentablemente tengo que sacar mi yo egoista.

Saludos,

Delphius
28-05-2015, 02:17:44
Mamx estuve examinando tu propuesta. Le faltaría pulir algunos detalles menores, pero puede que vaya bien encaminada.
Mi duda es ahora, si en el Create del TFileStream hay una excepción teóricamente con el uso del finally no hay forma de detectar que el archivo ha sido leído realmente.
Es más no tiene lugar siquiera el ReadMatrix que propones y no se realiza nada. Tal excepción pasa desapercibida, puesto que es una acción automática que realiza el compilador. Cuand hay una excepción en un constructor se suspende tal reserva de memoria y tiene lugar una liberación de los recursos que si hayan tenido lugar. El método LoadMatrix que propones no dejará a la matrix cargada con los datos del archivo, obviamente, pero no hay tal aviso.

Ese es el punto central. TFileStream es un caso particular en donde las reglas de juego no son tan genéricas. Normalmente no es de esperar un error en un constructor, pero el TFileStream explícitamente fue diseñado para arrojarlas si no ha sido posible crear o cargar el archivo y como tal hay que dar aviso de ello.

Mira esto:


constructor TFileStream.Create(const AFileName: string; Mode: Word);

begin
Create(AFileName,Mode,438);
end;


constructor TFileStream.Create(const AFileName: string; Mode: Word; Rights: Cardinal);

begin
FFileName:=AFileName;
If (Mode and fmCreate) > 0 then
FHandle:=FileCreate(AFileName,Mode,Rights)
else
FHAndle:=FileOpen(AFileName,Mode);

If (THandle(FHandle)=feInvalidHandle) then
If Mode=fmcreate then
raise EFCreateError.createfmt(SFCreateError,[AFileName])
else
raise EFOpenError.Createfmt(SFOpenError,[AFilename]);
end;


Y no sólo así son las cosas, que hasta los ReadBuffer y el WriteBuffer den excepción:


procedure TStream.ReadBuffer(var Buffer; Count: Longint);

Var
r,t : longint;

begin
t:=0;
repeat
r:=Read(PByte(@Buffer)[t],Count);
inc(t,r);
until (t=Count) or (r=0);
if (t<Count) then
Raise EReadError.Create(SReadError);
end;

procedure TStream.WriteBuffer(const Buffer; Count: Longint);

var
r,t : Longint;

begin
T:=0;
Repeat
r:=Write(PByte(@Buffer)[t],Count);
inc(t,r);
Until (t=count) or (r=0);
if (t<Count) then
Raise EWriteError.Create(SWriteError);
end;


Se que tu propuesta es mucho más limpia, y va muy bien encaminada. Nomás me pregunto sobre la posibilidades de como debiera de tratarse esta clase tan particular.

Saludos,

mamcx
28-05-2015, 04:00:27
Lo de arreglar mis problemas, no creo que desaparezcan prontamente. Va a ser una batalla dura seguramente.

Lo que dices tiene mala pinta, e internet es un pésimo lugar pa tratar temas tan serios. Solo dire que puede ser buena idea considerar pedir ayuda profesional, hacer denuncias a la justicia si es del caso, y buscar consejo de personas capacitadas en manejar esos asuntos.

Delphius
28-05-2015, 04:08:53
Lo que dices tiene mala pinta, e internet es un pésimo lugar pa tratar temas tan serios. Solo dire que puede ser buena idea considerar pedir ayuda profesional, hacer denuncias a la justicia si es del caso, y buscar consejo de personas capacitadas en manejar esos asuntos.

Lo se mamx. Sólo me permito un poco de catársis, que ya llevo bastante tiempo guardando si no exploto. Seguro que voy a recurrir a la justicia, aunque ya lo he intentado otras 3 veces. Obvio que es cosa seria, bastante. Pero si me permito la licencia de decirlo por acá es porque tengo la confianza suficiente y porque estoy decidido a acabar con esto de manera personal ni bien retorne.

Saludos, y gracias por tus consejos.

mamcx
28-05-2015, 04:14:55
Mi duda es ahora, si en el Create del TFileStream hay una excepción teóricamente con el uso del finally no hay forma de detectar que el archivo ha sido leído realmente.

Es más no tiene lugar siquiera el ReadMatrix que propones y no se realiza nada.

No entiendo cual es el bloqueo mental ;).

Si no se puede acceder al archivo, en que universo tiene sentido que lea la matriz?

Y que tiene que ver con que una excepcion surga del constructor? De que sirve que te deje crear el objeto, luego tengas un metodo "Open" y este falle... y luego que? Igual no puedes hacer nada.

Me tomo un tiempo darme cuenta, pero un objeto hecho asi es idiota:



TManejaUnRecursoComoBDArchivoRedEtc = class
public
constructor Create;
destructor Destroy; override;
procedure Open();
procedure Close();
procedure Write();
procedure Read();
end;



La razon? Ningun otro metodo tiene sentido si Open falla! Toca estar chequeando "Hey, de verdad puedo seguir?" y repetir en N-lugares "No se puede, porque la conexion no esta abierta".

Mucho mejor Asi:



function openRecurso():TManejaUnRecursoComoBDArchivoRedEtc
begin
//Aqui abrimos
// Y retornamos la clase. Ya sabemos que esta abierta y simplificamos todo el codigo.
end;

TManejaUnRecursoComoBDArchivoRedEtc = class
public
constructor Create(abierto:TRecurso);
procedure Close(); //Solo si en lenguajes con GC, porque Delphi se puede hacer close en el destroy

//Ahora todo tiene sentido!
procedure Write();
procedure Read();
end;


Este es un ejemplo donde el manejo del estado (manual) tiende a complicar las cosas.

---

Recuerda lo de la idea del "supervisor"? Bueno, Quien debe decidir que sucede si las cosas fallan es quien invoque a "LoadMatrix". La razon?

Porque asi como si no puesdes abrir el archivo, no tiene sentido invocar escrituras y lecturas - y el *porque no abrio el archivo* no IMPORTA en ese caso, porque IGUAL no puedes ni leerlo ni escribirlo!; de la misma manera, si *LoadMatrix* falla, el que lo invoco igual se frejo, que alla sido por la razon que sea? NO IMPORTA. Porque igual, no se cargo la matriz!

Ahora, lo que realmente sigue es pensar: Cual es la estrategia de supervision. Reintento? Aborto? Espero a que llamen al tecnico? Pongo eso en un log?

Nota como el manejo de la "ruta feliz" solo le importa que sucede cuando hay exito, y el supervisor solo le importa cuando hay fracaso. Pero conceptualmente, es bueno tenerlos separados.

Nota: "conceptualmente". No hay ningun lio en poner eso dentro de un try/finally. Lo unico importante, es pensar: Esto es si todo ok, esto es si no. Y no mezclar ambos, en la medida de lo posible (asi como el acto de poder abrir un recurso es una cosa, y poder operar sobre el recurso, otra).

Delphius
28-05-2015, 05:40:31
Mamx, el punto es que ahora tu diseño del LoadMatrix() hace que la clase supervisora de TArrayConverter necesite de mayor decisión sobre el estado del archivo y quien deba darle las garantías a su subordinada.
Lo que sugieres es pasar la pelota más arriba y dejar a los "supervisores" a que hagan más trabajo, cuando la idea es justamente que cuando una clase delega algo a otra esta responda bien. Llevado al extremo tendrás clases que no saben delegar nada.

Para este caso la clase TArrayConverter dice, che mirá tengo este error pero te lo dejo a vos y arreglatelas... a mi o me das las cosas masticadas o no te hago nada. Es más el tipo de excepción que se está propagando es algo que no debiera de esperar la(s) clase(s) supervisora(s), sino de algo más interno y que le compete al TArrayConverter. TClaseSupervisora (por darle un nombre abstracto) no debe ni tiene porqué saber que TArrayConverter utiliza TFileStream ni debiera estar lidiando con situaciones de error relacionadas con ésta ni de temas de archivos. ¡Para eso está TArrayConverter!
Por ello es importante capturar estas situaciones cuando se trabaja con archivos. Es muy necesario alertar si se tiene o no acceso a tal recurso. Estas situaciones se pueden dar en dos momentos: 1) al intentar crear dicha clase y acceder al archivo. 2. O bien, al momento de escribir o leer de él.
La situación 2 podemos controlarlo, con el típico diseño try/except. Más el caso patológico es el 1. Lo cual invalida el uso de una instancia de la clase TFileStream y por consiguiente no puede tener lugar el caso 2.

Por todo esto cualquier clase que haga uso de TFileStream debiera de poder controlar ambas situaciones y ofrecer un diseño que responda a esto y no estar relegandole más funciones a su clase cliente (la que tu llamas supervisora).

Saludos,

Casimiro Notevi
28-05-2015, 10:05:43
Como indica mamcx, creo que estás cayendo en algo que todos hemos caído muchas veces (debe ser algo implícito en personas como nosotros) y es que siempre estamos buscando la perfección. Hace tiempo que me di cuenta que no es necesario ser perfecto, basta con controlar lo "imprescindible" y si falla algo, ahora sí, emitir un mensaje o guardar un log, para saber dónde ha fallado y solucionarlo.
Pero intentar ser perfecto hasta el infinito y más allá no vale la pena :)

Delphius
28-05-2015, 16:32:19
Como indica mamcx, creo que estás cayendo en algo que todos hemos caído muchas veces (debe ser algo implícito en personas como nosotros) y es que siempre estamos buscando la perfección. Hace tiempo que me di cuenta que no es necesario ser perfecto, basta con controlar lo "imprescindible" y si falla algo, ahora sí, emitir un mensaje o guardar un log, para saber dónde ha fallado y solucionarlo.
Pero intentar ser perfecto hasta el infinito y más allá no vale la pena :)
No es que busque hacerlo perfecto, sólo espero controlar la separación cohesiva.
Para mi diseño es muy necesario poder controlar esta parte puesto que responde a una necesidad importante. No es un detalle menor el guardar y leer de archivos y que si algo falla vaya y pase. Me son vitales para que justamente en caso de problemas, y por sobre todo para poder continuar con el trabajo en otra oportunidad, tener un medio en donde almacenar los datos.
Justamente la clases clientes esperan delegar el trabajo de poder leer y guardar en archivos en esta clase TArrayConverter para mantenerse lo más cohesivos posibles. En vista a que en varios puntos del programa me es necesario estar guardando y leyendo de archivos la clase TArrayConverter es más que justificada.
No vale la pena el uso de bases de datos, de hecho inicialmente lo estuve considerando pero tener una representación matricial en una DB es algo más trabajoso.

Saludos,

mamcx
28-05-2015, 18:23:17
Asi que la pregunta es:

Que se supone que debe pasar si falla (por cualquier motivo) una llamada a TArrayConverter?

Delphius
28-05-2015, 21:06:38
Asi que la pregunta es:

Que se supone que debe pasar si falla (por cualquier motivo) una llamada a TArrayConverter?

Mi principal objetivo es que TArryConverter reporte a las clases clientes de que hubo problema de "conversión" (aunque quizá el término más adecuado sería materialización). Pero no si antes de evaluar si es posible detectar el tipo de problema que ha tenido y de intentar solucionarlo por otra vía. Pero si no es viable esto, al menos ofrecer una excepción acorde a lo que la clase deba reportar.

Una excepción si es posible evitar propagarla lo más "arriba" posible y solucionarlo antes mejor.

El constructor del TFileStream puede fallar debido a "permisos". El constructor recibe como parámetro el modo de apertura del archivo, y se cierra cuando el TFileStream es liberado. La 1ra versión del código, la que expuse, no tiene la lógica para reintentar una apertura con diferentes modos. Pero debiera de considerarlo.

El código que expuse fue mi 1er intento, y ni bien lo vi me he dado cuenta de que no es del todo seguro y sano. Esto me hace cuestionar la forma en como debiera de poder encarar su diseño y ofrecer medidas de seguridad que garanticen lo más posible de que se haga el trabajo.
Por ello es que vengo aquí. A ver alternativas.

Hasta el momento había previsto excepciones en el contexto de la clase TArrayConverter para los siguientes casos:
1. Que la matriz (o vector; debe ser posible de trabajar con ambas estructuras. No vi necesario hacer una clase específica para cada una) no esté en un estado inconsistente debido a que no estuviera reservada o no fuera uniforme. Esto se da cuando no pasa el examen de CheckMatrix.
2. Que el formato de archivo no sea válido. Esta excepción puede darse si efectivamente se ha accedido a la apertura del archivo.
3. Que la dimensión de la matriz y la cantidad de datos registrados en el archivo no sean coincidentes.

Para cada una hay una posible solución desde el lado cliente:
Para 1 y 3 (re)Dimensionar la matriz.
Para 2. Simplemente se descarta el archivo y se da aviso de que el archivo no es el indicado y se solicita otro para efectuar las operaciones previstas.

Para estas excepciones las clases clientes pueden y tienen la información necesaria para revertir el problema. Ya tienen su carga de trabajo.
No sería bueno que además tuviera que estar al tanto de errores relacionados con más operatoria interna, si es posible que TArrayConverter haga parte de ese trabajo.

Asi lo estoy entendiendo yo.

Saludos,

mamcx
29-05-2015, 18:38:01
Asi como lo expresas, entonces me parece que es bueno seguir tal como es FileStream. Crear TArrayConverter si el archivo no se puede leer carece de sentido, mientras que operar sobre TArrayConverter puede no siempre tener problemas, asi que en cada metodo se evalua que hacer.

Mejor dicho:


TArrayConverter = class
private
FFile: TFileStream;
..
..
public
//Aqui se hace lo de LoadFile. si esto falla, el objeto
//no tiene razon de existir
constructor Create(FileName: string);
destructor Destroy; override;
procedure LoadMatrix(AMatrix: TAMatrix);
procedure LoadVector(AVector: TAVector);
procedure SaveMatrix(AMatrix: TAMatrix; OnDir: TArrayOrientation);
procedure SaveVector(AVector: TAVector);
end;


Asi que quien llama a TArrayConverter con el archivo X solo tiene 2 opciones: Se puede o no operar sobre el archivo, si no se puede, ya haces como has dicho.

Si el objeto TArrayConverter existe, los errores son solo probables y el objeto reacciona de acuerdo.

Asi se captura de forma muy explicita lo que estas diciendo, sin complicar la logica interna del objeto. Ademas, mientras exista TArrayConverter se asume que el archivo esta en uso, lo que anula la variable de InUse que existe ahora porque TArrayConverter esta en un estado potencialmente dual: Tiene o no acceso?


Si entiendo bien lo de punto 1 & 3, entonces no veo porque pasas la matriz, en vez de retornarla tal como indique el archivo, o sea:


function LoadMatrix():TAMatrix;


Ademas, si estas invocando de multiples sitios ese metodo, tendras problemas de concurrencia y tendrias que aplicar bloqueos u otra opcion para asegurar el acceso concurrente.

Es mas simple cuando los objetos son inmutables, y la informacion no se comparte (crea un cuello de botella). Mientras no se muchisimos datos, es muy rapido recrear una matriz y que cada parte del programa tenga su propia copia sabiendo con certeza que nadie la va a alterar.

Al González
29-05-2015, 18:45:56
Hola Marcelo.

Cuatro cosas:

1. Gracias por regresar al foro. Da gusto ver cómo durante el último año han estado integrándose y reintegrándose muchos colegas en el Club. Muy de la mano, es evidente que el rescate de Delphi se va consolidando (y por añadidura el repunte de otros lenguajes Object Pascal). :)

2. En México tienes abiertas la puertas de mi humilde hogar, si te agrada la idea y te es posible viajar, no tienes más que avisar. Y si es necesario buscamos la forma de facilitarte el traslado. Entre nosotros hay mucho código y conocimiento que podríamos compartir presencialmente. En fin, es una invitación a que te desconectes aunque sea unos meses de aquel ambiente.

3. Sostengo los comentarios técnicos que escribí hace varios años en el hilo que refieres, incluso ahora estoy más convencido de ellos.

4. Si este fin de semana no me distraen mucho acá, revisaré con detenimiento tu caso y responderé aquí con lo que pueda ayudar.

Saludos.

Al.

Delphius
29-05-2015, 20:01:31
Asi como lo expresas, entonces me parece que es bueno seguir tal como es FileStream. Crear TArrayConverter si el archivo no se puede leer carece de sentido, mientras que operar sobre TArrayConverter puede no siempre tener problemas, asi que en cada metodo se evalua que hacer.

Mejor dicho:

Código Delphi [-] (http://www.clubdelphi.com/foros/#) TArrayConverter = class private FFile: TFileStream; .. .. public //Aqui se hace lo de LoadFile. si esto falla, el objeto //no tiene razon de existir constructor Create(FileName: string); destructor Destroy; override; procedure LoadMatrix(AMatrix: TAMatrix); procedure LoadVector(AVector: TAVector); procedure SaveMatrix(AMatrix: TAMatrix; OnDir: TArrayOrientation); procedure SaveVector(AVector: TAVector); end;


Asi que quien llama a TArrayConverter con el archivo X solo tiene 2 opciones: Se puede o no operar sobre el archivo, si no se puede, ya haces como has dicho.

Si el objeto TArrayConverter existe, los errores son solo probables y el objeto reacciona de acuerdo.

Asi se captura de forma muy explicita lo que estas diciendo, sin complicar la logica interna del objeto. Ademas, mientras exista TArrayConverter se asume que el archivo esta en uso, lo que anula la variable de InUse que existe ahora porque TArrayConverter esta en un estado potencialmente dual: Tiene o no acceso?


Si entiendo bien lo de punto 1 & 3, entonces no veo porque pasas la matriz, en vez de retornarla tal como indique el archivo, o sea:

Código Delphi [-] (http://www.clubdelphi.com/foros/#)function LoadMatrix():TAMatrix;


Ademas, si estas invocando de multiples sitios ese metodo, tendras problemas de concurrencia y tendrias que aplicar bloqueos u otra opcion para asegurar el acceso concurrente.

Es mas simple cuando los objetos son inmutables, y la informacion no se comparte (crea un cuello de botella). Mientras no se muchisimos datos, es muy rapido recrear una matriz y que cada parte del programa tenga su propia copia sabiendo con certeza que nadie la va a alterar.

Muchas gracias mamx (no recuerdo bien si tu nombre era Mario, y a mi me gusta en lo posible dirigirme más en forma personal) por tu valioso aporte y ayudarme.

De lo que estoy entendiendo de tu propuesta, es hacer de TArrayConverter una especie de Adapter del TFileStream y que en caso de poder crear una instancia de TArrayConverter proceda a utilizarla. De ser así en realidad no soluciona el mayor problema: que no se pueda crear el TArrayConverter es lo mismo que no se pueda crear el TFileStream.
Tal diseño directamente pone en evidencia que no tiene sentido la clase y directamente se haga uso de TFileStream ¿no crees?
Entonces las clases que eran clientes de TArrayConverter, que directamente, hagan uso de TFileStream.

Si yo estoy entendiendo mal el concepto por favor hazmelo saber. :o

La intención de contar con TArrayConverter es que ésta pueda centrar el trabajo común de leer y guardar de archivos. Otros módulos/clases tienen ya sus propios juegos de matrices y vectores. Entre ellas se comparten algunas estructuras comunes, y otras son propias. Cada módulo/clase aplica sus instrucciones sobre estas estructuras y varias son de gran importancia e interés poder materializarlas en un archivo para usos posteriores.
Debido a ello es que vi natural el que exista una instancia de TArrayConverter a modo singleton que reciba las estructuras de cualquiera de estos módulos/clases y haga lo que mejor sabe hacer.

No consideré prudente que un LoadMatrix() regrese el tipo de dato TMatriz como sugieres debido a que esto condiciona a que el conflicto de intereses entre quien es el dueño de la matriz y no incluirle lógica que ya es más propia de otras clases.

Por cuestiones de operatoria y diseño es raro que se necesite un intento de leer y/o guardar archivos de forma concurrente o simultáneo. Generalmente se da cierto orden secuencial. Pero por seguridad, y para esos casos en que tales archivos sean grandes (según pruebas algunos archivos si que serán grandes... entre los 4MB a 10MB en promedio pero puede darse situaciones de mayor tamaño), es que vi sano el añadir la propiedad InUse o alguna tipo flag que indique que el objeto está ocupado trabajando en ese momento. De ese modo pretendía dos cosas:
1. Que TArrayConverter cree el TFileStream y lo libere cuando se necesite trabajar con algún archivo (recién me percato que posiblemente sea un error disponer de un atributo privado)
2. Que al disponer de esta propiedad InUse permita cierto "relajo" a la aplicación y permita darle respiros ante la cantidad de operaciones que se realizan entre cada lectura/guardado de archivos.

Hola Marcelo.

Cuatro cosas:

1. Gracias por regresar al foro. Da gusto ver cómo durante el último año han estado integrándose y reintegrándose muchos colegas en el Club. Muy de la mano, es evidente que el rescate de Delphi se va consolidando (y por añadidura el repunte de otros lenguajes Object Pascal). :)

2. En México tienes abiertas la puertas de mi humilde hogar, si te agrada la idea y te es posible viajar, no tienes más que avisar. Y si es necesario buscamos la forma de facilitarte el traslado. Entre nosotros hay mucho código y conocimiento que podríamos compartir presencialmente. En fin, es una invitación a que te desconectes aunque sea unos meses de aquel ambiente.

3. Sostengo los comentarios técnicos que escribí hace varios años en el hilo que refieres, incluso ahora estoy más convencido de ellos.

4. Si este fin de semana no me distraen mucho acá, revisaré con detenimiento tu caso y responderé aquí con lo que pueda ayudar.

Saludos.

Al.

Hola Al, gracias por venir en mi ayuda. Pido disculpas por haberte molestado en forma privada pero es que ya mi cabeza no trabaja tan bien después de haberme mandado cerca de 5000 líneas de código en otros módulos previos a éste. Y sumándose a que por cosas de la vida ya he perdido mucha práctica al estar bastante alejado de la programación.

Si bien tengo más presencia en los últimos tiempos en DA, no quiere decir que no estime a algunos compañeros. Como te dije: la comunidad Delphi es una.

Te agradezco la invitación, y admito que tengo ganas de buscar otros aires. Ganas no me faltan de ir a México y visitar a toda la pandilla, pero por ahora no podrá ser. Ya en los próximos días debo volver a casa, acá tengo a conocidos que me están dando apoyo pero también me ponen en ultimatum para que concrete para éste Lunes 1 (fecha en que posiblemente viaje).
Ni modo, no es fácil explicar a quien no está en el tema que un sistema no puede estar a medias. No es que se puede dejar como esté y que ande.

Deberé regresar y ver el modo de terminarlo allí.
No pensé que esto me tomara tanto tiempo. Necesito mínimo otra semana más si no hay más imprevisto y todo sale a la perfección.


Te agradezco cualquier recomendación.

Les comento a ambos que en lo que estoy pensando es aplicar una lógica que siga este diseño:

procedure TArrayConverter.LoadMatrix(...)
var File: TFileStream;
begin
try
File := TFileStream.Create(...);
try
// Hacer todo el trabajo
finally
File.Free;
end;
except
// capturar excepciones dadas por TFileStrem y/o propagar las propias de TArrayConverter
end;
end;

Lo que estoy divagando es ver como adaptar el algoritmo que puse en mi primer post a este esquema lo más limpio posible. ¿Que piensan?

De este modo alguna clase que llame a ésta haga algo como:

procedure TOtraClase.HacerAlgo;
begin
HagoAlgoConEstaMatriz(LaMatrix);
// y otras cosas más...
try
Conversor.SaveMatrix(LaMatrix, NombreDelArchivo, Orientacion); // Conversor es singleton!
except
E: EFileOperation do
....
end;

Y la versión análoga para una lectura.

¿Como lo ven?

Saludos,

Delphius
30-05-2015, 06:50:12
Actualizo el hilo para comentarles que he hecho algunos cambios y redefinido un poco el diseño de la clase. He intentado hacer que el código siguiera la estructura de dos try anidados. El try-finally interior asume las condiciones ideales, y el exterior es un try-except para capturar las posibles excepciones que podrían darse.

la 2da versión, y quizá un poco más pulida, es:


procedure TArrayConverter.LoadMatrix(AMatrix: TAMatrix; FileName: string);
var idx, i, j, RowsM, ColsM, ErrM: integer;
AFile: TFileStream;
Can: Boolean;
Fmt: TFileFormat;
Header: TMatrixHeader;
begin
Can := CheckMatrix(AMatrix, RowsM, ColsM, ErrM);
try
AFile := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
Try
FIsBusy := true;
// Leemos formato y Header
Fmt.SizeFile := AFile.Seek(0, soEnd);
AFile.ReadBuffer(Fmt.IDIni, SizeOf(Fmt.IDIni)); // ID.Ini
AFile.ReadBuffer(Header, SizeOf(Header)); // Header
AFile.Seek(INI_ID_END, soFromEnd);
AFile.ReadBuffer(Fmt.IDEnd, SizeOf(Fmt.IDEnd)); // ID.End

// Posibles excepciones de formato y Header
if NOT IsValidFormat(Fmt, Header)
then raise EInvalidFileArrayFormat.Create(Format(sInvalidFormat,['matrix']));
if (Header.Cols <> ColsM) OR (Header.Rows <> RowsM)
then raise EInconsistArray.Create(sInconsistArray);

// Operamos
if Header.Orientation = aoCol
then for Idx := 1 to (Header.Rows * Header.Cols) do
begin
i := (Idx - 1) mod Header.Rows;
j := (Idx - 1) div Header.Rows;
AFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
end
else for Idx := 1 to (Header.Rows * Header.Cols) do
begin
i := (Idx - 1) div Header.Cols;
j := (Idx - 1) mod Header.Cols;
AFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
end;
finally
Afile.Free;
fIsBusy := false;
end; // end-try-finally
except
on E: EFOpenError do
begin
raise EFileAccessDenied.Create(Format(sAccessDenied, [FileName, E.Message]));
end;
on E: EReadError do
begin
raise EConvertFailed.Create(Format(sConvertFailed, ['read', FileName, E.Message]));
end;
// ¿Este except captura las excepciones lanzadas en el try interno y debiera relanzarlas?
end; // end-try-except
end;


La duda que me surge, es si el try-except externo captura las excepciones arrojadas por el interno, y de ser así debiera de relanzarlas.
El compilador no protesta, pero no he probado el código por falta de tiempo y ya me gana el cansancio.
En la versión anterior, al evaluar el estado de la matriz con CheckMatrix() primero verificaba el resultado de dicha operación y a posterior comprobaba las variables de control que éste regresa (RM, CM) con la información leída del Header para determinar si efectivamente la matriz y el archivo tengan la misma dimensión y por tanto ni sobre ni falten datos.

Ahora directamente no hago esta distinción y asumo que ambos escenarios son del mismo tipo de error. Delego en la clase cliente la tarea de verificar tanto que la matriz efectivamente esté disponible (y por tanto al invocarse a CheckMatriz se lea su tamaño correcto) como la de que controle lo mejor posible que sus archivos estén en orden. Por diseño de CheckMatriz cuando la evaluación falla regresa un "código" de error distinto a OPERATION_DONE las variables de control se establecen en -1.

Rediseñé las excepciones, y propuse una nueva forma de nombrarlas.

Saludos,

Delphius
31-05-2015, 19:59:11
Sigo avanzando.
Esta nueva propuesta está dentro de todo funcionando, según las pruebas que he estado llevando a cabo. El diseño si captura las excepciones generadas por TFileStream y genera las propias.

Un típico modo de uso, sería algo:

try
LogOperation('SaveMatrix');
Converter.SaveMatrix(myM1, TArrayOrientation(rgDir.ItemIndex + 1), myFile);
LogFile(myFile);
except
on E: EConverterException do
LogException(E);
end;

LogOperation() y LogException() son dos métodos que he implementado en un sistema básico para prueba de caja blanca (y algo de "caja gris") que van registrando en un Memo a modo Log cada test que se realiza.
En el caso de LogException() se busca capturar las excepciones y mostrar el nombre de la clase y el mensaje.

He advertido que el algoritmo tiene dos bugs que ya he procedido a eliminar:
1. Al momento de hacer una lectura del Identificador inicial debiera de invocar, por seguridad es apropiado hacer previamente un Seek(0, soFromBeginning)
2. Al momento de proceder a leer los datos de igual forma se debe posicionarse en el primer elemento y para ello es necesario un Seek(INI_DATA_M, soFromBeginning) siendo INI_DATA_M una constante apropiada para el caso de un archivo diseñado para matrices.

Estos mismos problemas detectados fueron eliminados en el método LoadVector.

Esta versión optimiza el indexado del posicionamiento al leer la data. Inicialmente procedía con dos ciclos anidados. Gracias a la tan bella matemática se puede prescindir de un ciclo y directamente hacer la correspondencia entre el índice Idx del dato y su posición [i, j] en la matriz tanto en un lectura columna por columna como fila a fila.

Estoy abierto a las sugerencias.

Saludos,

Al González
01-06-2015, 18:57:11
Hola Marcelo.

Este fin de semana tuve algo de tiempo libre para leer con detenimiento todo lo planteado por ti y Mario. ¡Cuántas cosas me gustaría decir! Como procurar la redacción en un sólo idioma y de manera más comprensible. O evitar en lo posible escribir métodos de más de 20 líneas de código. Pero intentaré enfocarme en lo principal de tu planteamiento técnico.

El uso canónico de un bloque Try-Finally es:
// 1. Código para hacer algo que deseamos luego deshacer, ocurra lo que ocurra. Crear, abrir, poner...
Try
// 2. Código que opera sobre lo hecho por el punto 1 y que tiene potencial de elevar una excepción.
Finally
// 3. Código que deshace lo hecho antes del Try. Destruir, cerrar, quitar...
End;
El uso canónico de un bloque Try-Except es:
// 4. Código para hacer algo que, en caso de ocurrir un problema, quisiéramos deshacer. Crear, abrir, poner...
Try
// 5. Código que podría generar alguna excepción sobre la cual deseamos tener control.
Except
{ 6. Código que deshace lo hecho antes del Try (destruir, cerrar, quitar...) y/o trata de forma particular la
excepción generada (por clase, código de error, etc.). }
End;
El punto 6 consiste, generalmente, en tomar alguna diligencia de control, liberar algún recurso que previamente se cargó en memoria, registrar o reportar una bitácora de incidencias, o anexar/convertir la excepción en otra excepción. Pero casi siempre este tratamiento termina elevando una excepción (propagando la misma o instanciando otra) al llamador. En algunas ocasiones el punto 4 podría ser opcional.

Por otra parte, además de la muy mala práctica de sofocar excepciones (atraparlas solo "para que no estorben"), existe cierta práctica no muy buena (y afortunadamente poco difundida) de atrapar una excepción para convertirla en un código de resultado. Nunca hagas eso a menos que no tengas alternativa. Es más válido que un método de aplicación envuelva a un método de API, atrapando una excepción de esa API para transformarla en una excepción más propia de la aplicación (algo que ya veo haces). Aunque también es válido dejar que la excepción original aparezca en la pantalla del usuario. Lo más malo es que aparezca cualquier mensaje de error, no que el mensaje de error sea algo "geek". Claro, podría ser deseable que las excepciones digan algo entendible, pero no olvidemos que son eso: excepciones que no debieran presentarse.

Dada la introducción anterior, me permito poner en seudocódigo cómo podría ser tu método LoadMatrix:

LoadMatrix
Begin
CheckMatrix (M); // CheckMatrix no sólo verifica, también eleva excepción en caso de encontrar invalidez.
—Punto 1. Crear FileStream

Try
—Punto 2. Uso del FileStream:
Try
—Punto 5. Leer archivo, verificar datos y elevar excepciones de tu clase en caso de inconsistencias.
Except;
—Punto 6. Atrapar excepciones conocidas o esperadas de FileStream y convertirlas en una excepción de tu clase.
End;
// Termina punto 2 (uso del FileStream).
Finally
—3. Destruir FileStream
End;
End;
Espero que esta participación sea de alguna ayuda. :)

Al González
01-06-2015, 19:05:17
Ah, pero resulta que el punto 1, es para ti punto 5 también. Entonces apliquemos la misma lógica:

LoadMatrix
Begin
CheckMatrix (M); // CheckMatrix no sólo verifica, también eleva excepción en caso de encontrar invalidez.

Try
—Punto 1 y 5. Crear FileStream

Try
—Punto 2. Uso del FileStream:
Try
—Punto 5. Leer archivo, verificar datos y elevar excepciones de tu clase en caso de inconsistencias.
Except;
—Punto 6. Atrapar excepciones conocidas o esperadas de FileStream y convertirlas en una excepción de tu clase.
End;
// Termina punto 2 (uso del FileStream).
Finally
—3. Destruir FileStream
End;
Except
—Punto 6. Atrapar excepciones esperadas de la creación del FileStream y convertirlas en una excepción de tu clase.
End;
End;
¿Mucha anidación?...¿Recuerdas lo de tener un límite de líneas? Divide un método largo en varios cortos y vencerás toda complejidad. ;)

nlsgarcia
01-06-2015, 20:46:53
Alberto,


...Msg #22 y Msg #23...

Excelente manejo de las excepciones ^\||/

Nelson.

Delphius
01-06-2015, 21:53:32
Hola Al.
Te agradezco tu aporte.

De lo que veo en tu propuesta, aplicas un triple try anidado y separas la excepción de la creación del FileStream de las excepciones que puede arrojar un Read o Write, y dejas a un try-finally intermedio cuando se dan situaciones "segura" y por tanto proceder a finalizar el Free.

No se diferencia mucho de la 2da propuesta que logré diseñar. Basicamente la diferencia radica en donde capturar y lanzar las excepciones propias.

Ayer estuve haciendo más pruebas y pude apreciar que no necesito relanzar la excepción como me estaba preguntando ¡Lo hace! Mi miedo era que el raise de EInvalidFileArrayFormat y/o el de EInconsistArray fuera capturado por el except y no se mostrara dicha excepción y tuviera que añadirla en la lista y volver a aplicar un rise. La excepción "pasa de largo" y efectivamente se termina recibiendo desde el "lado cliente".

Aparentemente todo funciona bien. La aplicación de prueba me ha permitido evaluar las alternativas, y en ciertos casos forzando algunas situaciones como un fallo de lectura/escritura. Se detectan las excepciones y se lanzan las adecuadas.

Tendría que probar tu alternativa, pero tengo que continuar con el trabajo. Por el momento tengo en suspenso tu propuesta. Y será el camino hacia una nueva versión si lo amerita, o bien en cuanto pueda disponer de tiempo y se justifique algo más seguro.

CheckMatrix no ha sido diseñada para lanzar excepciones. Pertenece a una unidad (que junto a otras forman un gran módulo) que contiene cientos de procedimientos y funciones dedicadas a álgebra lineal y otras cosas más. Su diseño, a la vieja escuela (paradigma estructurado), no ameritaba algo OO por lo que CheckMatrix, por ejemplo, se limita a ofrecer variables de control como ser el código de error generado y adicionalmente la dimensión de la matriz.
Es una unidad que ya ha alcanzado un grado de madurez bastante elevado y tiene casi 3000 líneas de código. No se malgasta en clases, excepciones, va a lo directo. Su diseño a sido dual: ofrece procedimientos bajo situaciones ideales, y además ofrece funciones y otros métodos de evaluación y control para aquellos casos en los que se necesita ciertas evaluaciones previas.

Las clases/módulos clientes de esta gran biblioteca si quieren pueden seguir el diseño estructurado o bien, aprovechar el poder OO para situaciones más complejas.

Los mensajes de algunas excepciones, si te fijas en mi 2da propuesta, aplican a un Format() a un string. Entre los parámetros paso el mensaje de la excepción original. De este modo el texto de la excepción que arrojo lleva consigo no sólo el texto para el contexto esperado (el Converter) sino además información adicional que provee sobre el TFileStream (aunque el texto de éstas más bien es básico según lo que vi en su código).

Lo de escribir en un único idioma, si es que te lo preguntas el porque uso comentarios en español y código en inglés, es porque ya me acostumbré asi. La idea es que los comentarios me ayuden a entender lo que tengo y para ello recurro al español. Después de todo es trabajo personal.

Lo que evitar algoritmos de más de 20 líneas lo tengo presente. Pero en ocasiones debemos extendernos más. Tengo por ejemplo en otra unidad un procedimiento que tiene 130 líneas de código. Se podría particionarlo, pero ya de por sí las operaciones se hacen de manera óptima y goza de buena velocidad. De particionarlo, algunas partes se estarían llamando cientos de veces. Preferí centrar todo y se ejecute en un único procedimiento antes que estar lidiando con las vueltas de código repartido en varios.

Te agradezco tu ayuda porque siempre se aprende algo nuevo. Seguro que esta lección del triple-try la pongo en práctica para otro caso, que tiene cierta semejanza con el TArrayConverter. Es otra especie de conversor pero para otras clases (quizá más manejables que el TFileStream... espero) y finalidades.

Seguramente nos mantendremos en contacto.

Saludos,

Al González
01-06-2015, 22:53:13
¡Mucha suerte, Marcelo!

Por cierto, fu forma de escribir y tus comentarios de código me parecen bien. Lo del idioma fue un llamado a la comunidad en general para ser un poco más formales con las explicaciones técnicas, evitando el spanglish en algo tan valioso como la divulgación técnica.

Al González.

P.D. Nelson, gracias por ese mensaje. :)

Delphius
01-06-2015, 23:31:48
¡Mucha suerte, Marcelo!

Por cierto, fu forma de escribir y tus comentarios de código me parecen bien. Lo del idioma fue un llamado a la comunidad en general para ser un poco más formales con las explicaciones técnicas, evitando el spanglish en algo tan valioso como la divulgación técnica.

Al González.

P.D. Nelson, gracias por ese mensaje. :)
Muchas gracias Al.
Ojalá la suerte fuera contagiosa y se transmitiera por cables de red. Necesito ¡ y mucha! :p

Me alagas demasiado por mi escritura de código. Aún tengo mucho por pulir, sobre todo habiendo perdido mucha práctica por haber tenido que dejar por tanto tiempo la programación. Y si de pulir se trata ahora que veo de nuevo mi código creo que puedo reducir 2 cálculos en uno sólo. Cuando calculo los índices i y j aplico DIV y MOD según sea el caso. Esto se puede hacer de manera efectiva y rápida con DivMod.

En lo de ser lo más técnico posible te apoyo. Hay que hacer lo posible por emplear las palabras formales, pero entiendo que en ocasiones por las prisas, cansancio, el no poder encontrar las expresiones en el momento (que pasa... y seguido) y hasta algo de desconocimiento, nos juegan en contra y nos mandamos cada una.

Saludos,