Ir al contenido


Foto

¿Una correcta manera de utilizar TFileStream capturando posibles excepciones variadas?

TFileStream Excepciones Archivos

  • Por favor identifícate para responder
6 respuestas en este tema

#1 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 23 mayo 2015 - 06:04

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:


delphi
  1. procedure TArrayConverter.LoadMatrix(AMatrix: TAMatrix; FileName: string);
  2. var Header: TMatrixHeader;
  3. Fmt: TFileFormat;
  4. i, j, RM, CM, Err, ConvE: integer;
  5. Can: boolean;
  6. begin
  7. Can := CheckMatrix(AMatrix, RM, CM, Err);
  8. if Can AND (Err = OPERATION_DONE)
  9. then begin
  10. try
  11. // Cargar el archivo
  12. FFile.Create(FileName, fmOpenRead or fmShareDenyWrite);
  13. // leemos formato
  14. FInUse := true;
  15. Fmt.SizeFile := FFile.Seek(0, soEnd);
  16. FFile.ReadBuffer(Fmt.IDIni, SizeOf(Fmt.IDIni)); // ID.Ini
  17. FFile.ReadBuffer(Header, SizeOf(Header)); // Header
  18. FFile.Seek(INI_ID_END, soFromEnd);
  19. FFile.ReadBuffer(Fmt.IDEnd, SizeOf(Fmt.IDEnd)); // ID.End
  20. // ¿Valido?
  21. if ISValidFormat(Fmt, Header)
  22. then begin
  23. // Supuesto tamaño de datos
  24. if (Header.Rows = RM) AND (Header.Cols = CM)
  25. then begin
  26. // Leemos data
  27. if Header.Orientation = aoCol
  28. then begin
  29. // ... por columnas
  30. for j := 0 to CM - 1 do
  31. for i := 0 to RM - 1 do
  32. FFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
  33. end
  34. else begin
  35. // ... por filas
  36. for i := 0 to RM - 1 do
  37. for j := 0 to CM - 1 do
  38. FFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
  39. end;
  40. end
  41. else ConvE := 3;
  42. end
  43. else ConvE := 2;
  44. finally
  45. FFile.Free;
  46. FInUse := false;
  47. end;
  48. end
  49. else ConvE := 1;
  50. // ¿Lanzamos excepción?
  51. case ConvE of
  52. 1: raise EInconsistentArray.Create('Check of matrix failed', itCheck);
  53. 2: raise EFileOperation.Create('Invalid matrix file format');
  54. 3: raise EInconsistentArray.Create('The dimensions of the matrix and file data do not match',
  55. itDimNotMatch);
  56. end;
  57. 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:


delphi
  1. TArrayConverter = class
  2. private
  3. FFile: TFileStream;
  4. FInUse: boolean;
  5. function IsValidFormat(AFormat: TFileFormat; AHeader: TMatrixHeader): boolean; overload;
  6. function ISValidFormat(AFormat: TFileFormat; AHeader: TVectorHeader): boolean; overload;
  7. function MakeHeader(Rows, Cols: integer; Orientation: TArrayOrientation): TMatrixHeader;
  8. overload;
  9. function MakeHeader(Dim: integer): TVectorHeader; overload;
  10. public
  11. constructor Create;
  12. destructor Destroy; override;
  13. procedure LoadMatrix(AMatrix: TAMatrix; FileName: string);
  14. procedure LoadVector(AVector: TAVector; FileName: string);
  15. procedure SaveMatrix(AMatrix: TAMatrix; OnDir: TArrayOrientation; FileName: string);
  16. procedure SaveVector(AVector: TAVector; FileName: string);
  17. property InUse: boolean read FInUse;
  18. 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.

Muchas gracias.


  • 0

#2 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 25 mayo 2015 - 02:19

De algunas búsquedas que he estado 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:


php
  1. FS := TFileStream.Create(...);
  2. try
  3. // Usar el FS
  4. finally
  5. FS.Free;

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?

 

Saludos,


  • 0

#3 escafandra

escafandra

    Advanced Member

  • Administrador
  • 4.111 mensajes
  • LocationMadrid - España

Escrito 26 mayo 2015 - 02:18

De algunas búsquedas que he estado 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:


delphi
  1. FS := TFileStream.Create(...);
  2. try
  3. // Usar el FS
  4. finally
  5. FS.Free;
  6. end;


Tienes toda la razón. La estructura es buena pero no se comprueba que el archivo se ha abierto. Aunque un error de apertura, a mi juicio, no debería ser una excepción, si debe controlarse para evitar el error en el resto del código y no dejarlo hasta que salte la excepción en el bloque try.

 

 

Saludos.


  • 0

#4 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 26 mayo 2015 - 09:38

Yo ya estoy medio perdido.

 

Recuerdo la discusión debate 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 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á?

 

Yo ya estoy confundido. :(

 

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.

 

Saludos,


  • 0

#5 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 28 mayo 2015 - 12:18

¿Alguna pista o sugerencia? :(

 

Saludos,


  • 0

#6 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 31 mayo 2015 - 12:50

Bueno, les comento. Ya tengo una nueva versión de mi algoritmo. Y las pruebas que ya estuve haciendo parecen ir de manera satisfactoria. Adjunto el código, que aprovecha el diseño de un doble try aninado. El interior, un try-finally, se encarga de toda la operatoria; el try-except del exterior captura posibles excepciones que lanza la clase TFileStream y lanza las apropiadas siguiendo la buena práctica del patrón "Convertir Excepciones".


delphi
  1. procedure TArrayConverter.LoadMatrix(AMatrix: TAMatrix; FileName: string);
  2. var idx, i, j, RowsM, ColsM, ErrM: integer;
  3. AFile: TFileStream;
  4. Can: Boolean;
  5. Fmt: TFileFormat;
  6. Header: TMatrixHeader;
  7. begin
  8. Can := CheckMatrix(AMatrix, RowsM, ColsM, ErrM);
  9. try
  10. AFile := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
  11. Try
  12. FIsBusy := true;
  13. // Leemos formato y Header
  14. Fmt.SizeFile := AFile.Seek(0, soEnd);
  15. AFile.Seek(0, soFromBeginning);
  16. AFile.ReadBuffer(Fmt.IDIni, SizeOf(Fmt.IDIni)); // ID.Ini
  17. AFile.ReadBuffer(Header, SizeOf(Header)); // Header
  18. AFile.Seek(INI_ID_END, soFromEnd);
  19. AFile.ReadBuffer(Fmt.IDEnd, SizeOf(Fmt.IDEnd)); // ID.End
  20.  
  21. // Posibles excepciones de formato y Header
  22. if NOT IsValidFormat(Fmt, Header)
  23. then raise EInvalidFileArrayFormat.Create(Format(sInvalidFormat,['matrix']));
  24. if (Header.Cols <> ColsM) OR (Header.Rows <> RowsM)
  25. then raise EInconsistArray.Create(sInconsistency);
  26.  
  27. AFile.Seek(INI_DATA_M, soFromBeginning);
  28. // Operamos
  29. if Header.Orientation = aoCol
  30. then begin
  31. for Idx := 1 to (Header.Rows * Header.Cols) do
  32. begin
  33. i := (Idx - 1) mod Header.Rows;
  34. j := (Idx - 1) div Header.Rows;
  35. AFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
  36. end
  37. end
  38. else begin
  39. for Idx := 1 to (Header.Rows * Header.Cols) do
  40. begin
  41. i := (Idx - 1) div Header.Cols;
  42. j := (Idx - 1) mod Header.Cols;
  43. AFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
  44. end;
  45. end;
  46. finally
  47. Afile.Free;
  48. fIsBusy := false;
  49. end; // end-try-finally
  50. except
  51. on E: EFOpenError do
  52. begin
  53. raise EFileAccessDenied.Create(Format(sAccessDenied, [FileName, E.Message]));
  54. end;
  55. on E: EReadError do
  56. begin
  57. raise EConvertFailed.Create(Format(sConvertFailed, ['read', FileName, E.Message]));
  58. end;
  59. // ¿Este except captura las excepciones lanzadas en el try interno y debiera relanzarlas?
  60. end; // end-try-except
  61. end;

Además he optimizado el indexado y posicionamiento dentro de la matriz. Inicialmente tenía dos ciclos para moverse sobre la posición [i, j]. Ahora, lo tengo con un único ciclo. Gracias a la matemática es que es posible determinar que posición ocupa dentro de la matriz un índice de los datos. Ya sea que la lectura se haga columna a columna o bien fila a fila.

 

Un proceso análogo se ha llevado para el LoadVector, y algo similar para sus respectivos Save's.

 

Lo que estoy estudiando en implementar ahora es si será necesario (y de utilidad) hacer una especie de Import/Transform para volcar el contenido de una matriz a un archivo vectorial, y desde un vector a un archivo matricial.

Lo que si veo que podría servirme es que de una matriz pueda guardar sus vectores columnas/filas en distintos archivos. Es decir, en lugar de tener un único archivo matricial, generar N archivos vectoriales, una especie de Particionado.

 

Se siguen aceptado sugerencias.

 

Saludos,


  • 0

#7 Delphius

Delphius

    Advanced Member

  • Administrador
  • 6.295 mensajes
  • LocationArgentina

Escrito 01 junio 2015 - 09:53

Y bueno, las pruebas fueron encaminadas. Y parece andar todo bien. Les comento que desde CD el compañero Al González y mamx me estaban dando una manito.

La propuesta final que me llegó Al es una variación a 3 trys del que tengo implementada (yo uso una a 2 try), que si bien parezca un tanto anidada podría ser más "exception-friendly".

Por el momento mi propuesta es suficiente.

 

Lo que si he detectado es que deberé ampliar las funcionalidades de la clase. No me había percatado que si necesitaré añadir funcionalidad para realizar lecturas en "trozos" (es decir en lugar de toda la matriz/vector) leer una parte de éste (como por ejemplo, un área de 3x3 de una matriz original de 5x4, y  no necesariamente sea desde el origen), y que incluso en ocasiones necesito leer solamente un conjunto de filas/columnas y para el caso de vectores ciertas posiciones.

Así que habrá que estudiar como dar forma a esto.

 

Saludos,


  • 0





Etiquetado también con una o más de estas palabras: TFileStream, Excepciones, Archivos

IP.Board spam blocked by CleanTalk.