language agnostic - ¿Cuándo está bien verificar si existe un archivo?
language-agnostic filesystems (18)
Los sistemas de archivos son volátiles. Esto significa que no puede confiar en que el resultado de una operación siga siendo válido para la siguiente, incluso si es la siguiente línea de código. No puede decir simplemente if (some file exists and I have permissions for it) open the file
, y no puede decir if (some file does not exist) create the file
. Siempre existe la posibilidad de que el resultado de su condición if
cambie entre las dos partes de su código. Las operaciones son distintas: no atómicas.
Para empeorar las cosas, la naturaleza del problema significa que si está tentado de hacer este control, es probable que ya esté preocupado o consciente de que algo que no controla es probable que le suceda al archivo. La naturaleza de los entornos de desarrollo hace que sea menos probable que ocurra este evento durante las pruebas y es muy difícil de reproducir. Por lo tanto, no solo tiene un error, sino que el error no se mostrará durante la prueba.
Por lo tanto, en circunstancias normales, lo mejor es ni siquiera intentar verificar si existe un archivo o directorio. En su lugar, ponga su tiempo de desarrollo en el manejo de las excepciones del sistema de archivos. De todos modos, debe manejar estas excepciones, de modo que este es un uso mucho mejor de sus recursos. Aunque las excepciones son lentas, verificar la existencia de un archivo requiere un viaje adicional al disco, y el acceso al disco es mucho más lento. Incluso tengo una answer bien elegida para este efecto en otra pregunta.
Pero estoy teniendo algunas dudas. En .Net, por ejemplo, si eso es realmente cierto, los métodos .Exists()
no estarían en la API en primer lugar. También considere los escenarios donde espera que su programa necesite el archivo creado. El primer ejemplo que se me ocurre es para una aplicación de escritorio. Esta aplicación instala un archivo de configuración de usuario predeterminado en su directorio de inicio, y la primera vez que cada usuario inicia la aplicación, copia este archivo en la carpeta de datos de la aplicación de ese usuario. Espera que el archivo no exista en ese primer inicio.
Entonces, ¿cuándo es aceptable verificar de antemano la existencia (u otros atributos, como el tamaño y los permisos) de un archivo? ¿Esperar que el fracaso en lugar del éxito en el primer intento sea una buena regla general?
Como política general, los métodos como File.Exists
o propiedades como WeakReference.Alive
o SomeConcurrentQueue.Count
no son útiles como medio para garantizar que exista un "buen" estado, pero pueden ser útiles como medio para determinar que un "mal" "el estado existe sin hacer ningún trabajo innecesario (y posiblemente contraproducente). Tales situaciones pueden surgir en muchos escenarios que involucran bloqueos (y archivos, ya que a menudo incluyen bloqueos). Debido a que todas las rutinas que necesitan bloquear un conjunto de recursos deben, siempre que sea práctico, obtener bloqueos en esos recursos en un orden constante, puede ser necesario adquirir un bloqueo en un recurso que se espera que exista antes de adquirir un recurso que pueda o puede que no exista En tal escenario, aunque es imposible evitar la posibilidad de que uno pueda bloquear el primer recurso, no adquirir el segundo, y luego liberar el primer bloqueo sin haber hecho ningún trabajo útil con él, verificando la existencia del segundo recurso antes adquirir la cerradura en la primera minimizaría el esfuerzo innecesario e inútil.
Como usted señaló, siempre es importante lo que el programa debería hacer si falta el archivo. En todas mis aplicaciones, el usuario siempre puede eliminar el archivo de configuración y la aplicación creará uno nuevo con valores predeterminados. No hay problema. También envío mis aplicaciones sin archivos de configuración.
Pero los usuarios tienden a eliminar archivos e incluso archivos que no deberían eliminar, como las claves de serie y los archivos de plantilla. Siempre compruebo estos archivos porque sin ellos la aplicación no se puede ejecutar en absoluto. No puedo crear una nueva clave de serie por defecto.
¿Qué debería pasar cuando falta el archivo? Puede hacer un buscador de búsqueda de archivos o excepciones, pero la verdadera pregunta es: ¿Qué sucederá cuando el archivo no se encuentre? O qué tan importante es el archivo para la aplicación. Comprobo todo el tiempo antes de intentar acceder a los archivos de soporte de la aplicación. Además, manejo el error si el archivo está dañado y no se puede cargar.
Creo que cada vez que sepa que el archivo puede existir o no y desea realizar alguna acción alternativa en función de la existencia del archivo, debe hacer el control porque en este caso no es una condición excepcional para que el archivo no exista. . Esto no lo absolverá de tener que manejar excepciones, de otra persona que elimine o cree el archivo entre el cheque y su abierto, pero deja clara la intención del programa y no se basa en el manejo de excepciones para realizar el flujo. -control de lógica.
EDITAR : Un ejemplo podría ser la rotación de registros al inicio.
try
{
if (File.Exists("app.log"))
{
RotateLogs();
}
log = File.Open("app.log", FileMode.CreateNew );
}
catch (IOException)
{
...another writer, perhaps?
}
catch (UnauthorizedAccessException)
{
...maybe I should have used runas?
}
Creo que el cheque tiene sentido cuando quieres estar seguro de que el archivo estaba allí en primer lugar. Como dijo los archivos de configuración ... si hay un archivo intentaré combinar las configuraciones existentes en lugar de eliminarlas.
Otros casos serían cuando un usuario me dice que haga algo con un archivo. Sí, sé que openFileDialog comprobará si existe un archivo (pero esto es opcional). Recuerdo vagamente en VB6 que este no era el caso, por lo que era común verificar el archivo que me dijeron que usara.
Prefiero no programar por excepción.
Editar
No me perdí el punto. Puede tratar de acceder al archivo, se lanza una excepción y luego, cuando vaya a crear el archivo, el archivo ya se colocó allí. Lo cual ahora hace que su código de manejo de excepciones sea frustrante. Así que supongo que podríamos tener un controlador de excepción en nuestro manejador de excepciones para detectar que el archivo cambió una vez más ...
Prefiero tratar de evitar excepciones, no usarlas para controlar la lógica.
Editar
Además, otro momento para comprobar atributos como el tamaño es cuando esperas que termine una operación de archivo, sí, nunca se sabe con seguridad pero con un buen algoritmo y, dependiendo del sistema que escriba el archivo, es posible que puedas manejar una buena cantidad de casos (Tenía un sistema en funcionamiento durante cinco años que miraba archivos pequeños que venían por ftp, y usa la misma API que el vigilante del sistema de archivos, y luego comienza a sondear esperando que el archivo deje de cambiar, antes de provocar un evento que el archivo está listo para ser consumido).
Creo que el motivo de "Existe" es determinar cuándo faltan archivos sin la necesidad de crear todos los datos de mantenimiento necesarios para acceder al archivo o tener excepciones. Por lo tanto, es una optimización de manejo de archivos más que cualquier otra cosa.
Para un solo archivo, el guardado que "Existe" da generalmente es insignificante. Si estaba comprobando si un archivo existe muchas, muchas veces (por ejemplo, buscando # archivos incluidos), entonces el ahorro podría ser significativo.
En .Net, la especificación para File.Exists no enumera ninguna excepción que el método pueda arrojar, a diferencia de File.Open, que enumera nueve excepciones, por lo que ciertamente hay menos comprobación en el primero.
Incluso si "Existe" devuelve verdadero, aún necesita manejar excepciones al abrir el archivo, como lo sugiere la referencia de .Net.
Depende de sus requisitos, pero una forma es tratar de obtener un identificador de archivo abierto exclusivo , con algún tipo de mecanismo de reintento. Una vez que tenga ese control, será difícil (o imposible) que otro proceso elimine (o mueva) ese archivo.
He usado código en .NET de forma similar a lo siguiente para obtener un identificador de archivo exclusivo, donde espero que algún otro proceso posiblemente escriba el archivo:
FileInfo fi = new FileInfo(fullFilePath);
int attempts = maxAttempts;
do
{
try
{
// Asking to open for reading with exclusive access...
fs = fi.Open(FileMode.Open, FileAccess.Read, FileShare.None);
}
// Ignore any errors...
catch {}
if (fs != null)
{
break;
}
else
{
Thread.Sleep(100);
}
}
while (--attempts > 0);
El método File.Exists existe principalmente para probar la existencia de un archivo cuando no tiene la intención de abrir el archivo. Por ejemplo, probando la existencia de un archivo de bloqueo cuya existencia misma te dice algo pero cuyos contenidos son inmateriales.
Si va a abrir el archivo, tendrá que manejar cualquier excepción, independientemente de los resultados de las llamadas anteriores a File.Exists. Por lo tanto, en general, no tiene ningún valor real llamarlo en estas circunstancias. Simplemente use el valor de enumeración FileMode apropiado en su método abierto y maneje cualquier excepción, tan simple como eso.
EDITAR: aunque está redactado en términos de la API .Net, se basa en la API del sistema subyacente. Tanto Windows como Unix tienen llamadas al sistema (es decir, CreateFile) que usan el equivalente de la enumeración FileMode. De hecho, en .Net (o Mono), el valor de FileMode simplemente se transfiere a la llamada subyacente del sistema.
En el entorno * nix, un método bien establecido para comprobar si ya se está ejecutando otra copia del programa es crear un archivo de bloqueo. Entonces, el control de existencia de archivos se usa para verificar esto.
Esto puede ser demasiado simplista, pero creo que la razón principal para verificar la existencia de un archivo (de ahí la existencia de .Exists ()) sería evitar sobreescrituras involuntarias de archivos existentes, no para evitar excepciones causadas al intentar acceder archivos inexistentes o no accesibles.
EDIT 2
Esto fue, de hecho, demasiado simplista y te recomiendo que veas la respuesta de Stephen Martin.
Hay muchas aplicaciones posibles que bien podría estar escribiendo que un simple archivo. Exists es más que adecuado para el trabajo. Si se trata de un archivo de configuración que solo usará su aplicación, entonces no es necesario que se exceda en su manejo de excepciones.
Aunque los "defectos" que ha señalado al usar este método son todos válidos, no significa que no sean fallas aceptables para algunas situaciones.
Para responder a mi propia pregunta (en parte), quiero ampliar el ejemplo que utilicé: un archivo de configuración predeterminado.
En lugar de verificar si existe al inicio de la aplicación e intentar copiar el archivo si la verificación falla, lo que hay que hacer es intentar copiar el archivo. Simplemente lo hace de tal manera que la copia fallará si el archivo existe en lugar de reemplazar un archivo existente. De esta forma, todo lo que necesita hacer es detectar e ignorar cualquier excepción lanzada si la copia falla debido a un archivo existente.
Si bien esta es una publicación independiente del idioma, parece que estás hablando de .NET. La mayoría de los sistemas (.NET y otros) tienen API más detalladas para determinar si el archivo existe al abrir el archivo.
Lo que debe hacer es hacer una llamada para acceder al archivo, ya que generalmente indicará a través de algún tipo de error que el archivo no existe (si realmente no lo hace). En .NET, debería pasar por la capa P / Invoke y usar la función CreateFile API. Si esa función devuelve un error de ERROR_FILE_NOT_FOUND, entonces sabrá que el archivo no existe. Si vuelve exitosamente, entonces tienes un mango que puedes usar.
El punto aquí es que es una operación algo atómica, que en última instancia es lo que estás buscando.
Luego, con el manejador, puede pasarlo a un constructor de FileStream y realizar su trabajo en el archivo.
Si le preocupa que alguien más elimine el archivo, quizás deba implementar algún tipo de sistema de bloqueo. Por ejemplo, solía trabajar en el código de C-News, un servidor de noticias de Usenet. Dado que muchas de las cosas que hizo podrían ocurrir de forma asíncrona, "bloquearía" un archivo o un directorio haciendo un archivo temporal, y luego vincularlo fuertemente a un archivo llamado "LOCK". Si el enlace fallara, significaría que alguna otra versión del programa estaba escribiendo en ese directorio; de lo contrario, sería suya y podría hacer lo que quiera.
Lo ingenioso de esto es que la mayor parte del programa estaba escrito en shell y awk, y este era un mecanismo de bloqueo muy portátil. Además, el archivo de bloqueo contendría el PID del propietario, por lo que podría mirar el archivo de bloqueo existente para ver si el propietario aún se estaba ejecutando.
Solo lo verificaría si esperaba que faltara (por ejemplo, la configuración de la aplicación) y solo si tengo que leer el archivo.
Si tengo que escribir en el archivo, es un archivo de registro (así puedo adjuntarlo o crear uno nuevo) o reemplazo su contenido, así que también podría volver a crearlo.
Si espero que el archivo exista, sería correcto que se genere una Excepción. El manejo de excepciones debe informar al usuario o realizar la recuperación. Mi opinión es que esto da como resultado un código más limpio.
La protección de archivos (es decir, no sobrescribir (posiblemente importante) archivos) es diferente, en ese caso siempre verificaría si existe un archivo, si el marco no hace eso por mí (piense en SaveFileDialog)
Su problema podría resolverse fácilmente con la informática básica ... leer en Semaphores .
(No quise sonar como un idiota, solo te estaba apuntando a una respuesta simple para un problema común).
Tenemos una herramienta de diagnóstico que tiene que reunir un conjunto de archivos, incluido el registro del instalador. Dependiendo de las diferentes condiciones, el registro del instalador puede estar en una de dos carpetas. Peor aún, puede haber diferentes versiones del registro en ambas carpetas. ¿Cómo encuentra la herramienta la correcta?
Es bastante simple si compruebas la existencia. Si solo hay uno presente, toma ese archivo. Si existen dos, encuentre cuál tiene la última hora de modificación y tome ese archivo. Esa es la manera normal de hacer las cosas.
Un ejemplo: es posible que pueda verificar la existencia de archivos que no puede abrir (debido, por ejemplo, a permisos).
Otro ejemplo, posiblemente mejor: desea verificar la existencia de un archivo de dispositivo Unix. Pero definitivamente no lo abras; abrirlo tiene efectos secundarios (por ejemplo, abrir / cerrar /dev/st0
rebobinará la cinta)
Una variedad de aplicaciones incluyen servidores web integrados. Es común que generen certificados SSL autofirmados la primera vez que se inician. Una forma directa de implementar esto sería verificar si el certificado existe al inicio, y crearlo si no.
En teoría, podría existir para el control, y no existir más tarde. En ese caso, obtendríamos un error cuando tratemos de escuchar, pero eso se puede manejar con bastante facilidad y no es un gran problema.
También es posible que no exista para el control y exista más tarde. En ese caso, se sobrescribe con un nuevo certificado o la escritura del nuevo certificado falla, según su política. El primero es un poco molesto, en términos del cambio de cert que causa alguna alarma, pero tampoco es realmente crítico, especialmente si usted hace un poco de registro para indicar lo que está sucediendo.
Y, en la práctica, ambos casos son extraordinariamente improbables.