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:
---
Cita:
|
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/arms...hesis_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.
Cita:
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:
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:
Código Delphi
[-]
function LoadFile(FileName: string):TFileStream;
begin
Result := FFile.Create(FileName, fmOpenRead or fmShareDenyWrite);
FFile.ReadBuffer(Fmt.IDEnd, SizeOf(Fmt.IDEnd)); end;
procedure ReadMatrix(AMatrix: TAMatrix; FFile: TFileStream; RM, CM:Int);
begin
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;
if Header.Orientation = aoCol
then begin
for j := 0 to CM - 1 do
for i := 0 to RM - 1 do
FFile.ReadBuffer(AMatrix[i, j], SizeOf(TYPEDATA));
end
else begin
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
