-4

One of the most annoying boneheaded bugs I run into is trying to create a new file in a directory that does not exist. (At least on Windows/NTFS, not sure about other OS/FS combinations.)

Here's how it usually happens in C#:

const string directoryName = @"logDir\";

// This would prevent the exceptions.
//Directory.CreateDirectory(directoryName);

const string logMessage = "Logging a line!";
try
{
    const string fileName = directoryName + "Log.txt";
    File.AppendAllText(fileName, logMessage + Environment.NewLine);
}
catch (DirectoryNotFoundException)
{
    Console.WriteLine("Yup, complaining about directory not found when trying to append.");
}

try
{
    const string databaseName = directoryName + "Log.sqlite";
    using (var connection = new SQLiteConnection($"Data Source={databaseName};Version=3;"))
    {
        connection.Open();
        using (var command = connection.CreateCommand())
        {
            command.CommandText = "CREATE TABLE IF NOT EXISTS log(message); INSERT INTO log(message) VALUES(@message);";
            command.Parameters.AddWithValue("@message", logMessage);
            command.ExecuteNonQuery();
        }
        connection.Close();
    }
}
catch (SQLiteException)
{
    Console.WriteLine("Yup, even sqlite, which will create the file if it doesn't exist, requires a separate step to create a directory.");
}

If the API provides the ability to append files or create them if it doesn't exist, why wouldn't that same ability also create the required directory if it doesn't exist? What is the rationale behind such a behaviour?

May be a dumb question, but I had to ask because I'm annoyed when an easy to miss extra step is required for code to work properly, and I don't see the benefits.

5
  • 4
    "Why did they choose to..." questions are unanswerable without a statement from the original author, so we can only speculate. But keep in mind that the seminal file system APIs were invented over 40 years ago, when disk space was incomparably slower and more expensive than today. It is hard for us to imagine how frivolous it would have appeared back then to implicitly create N additional inodes (for the directories) when someone only asks for one file. Commented Dec 14, 2017 at 10:47
  • The truth is because the creator of the API didn't bother. We can only guess at the rationale they had for it. I'd assume "because that's how it is done" is good enough. For a more logical reason, I'd suppose that creating a file is not synonymous with creating a directory and most OSs treat the two separately, so why should the API hide a step? If you want that functionality that's your responsibility. Woe betide the API that creates a bug by always creating the directory. Commented Dec 14, 2017 at 10:47
  • @KilianFoth I understand we can't know the exact design process the original API designers went through. I'm just wondering what some plausible good reasons might be. Commented Dec 14, 2017 at 10:50
  • Why not just write your own method that captures the exception and then retries the file create, creating any intermediate and not existing directories needed? Then use the utility function rather than the direct API. Commented Dec 14, 2017 at 12:50
  • @RichardChambers I'm not looking for solutions to this problem, I was just wondering what are the upsides of this multi-step process. Because I've never ever needed to create a directory in my application unless I was just about to put a file in there anyway. In other words, I've only ever created directories specifically to avoid this error. Commented Dec 14, 2017 at 14:06

2 Answers 2

3

When confronted to an erroneous input, an API has two choices:

  • raise an error, leaving any further decision to the caller
  • provide an error-free result by guessing what the caller's intention was

Usually the first choice is preferred: The API provides a clear and simple contract to its clients which lets them know as early as possible when an error occurs.

For instance, the Unix command mkdir is not able to create subdirectories on its own initiative:

mkdir foo/bar

raises an error.

Unless this is explicitely specified:

mkdir -p foo/bar

As to why creating a file in nonexistent subdirectory is considered an erroneous input, I bet that it comes from the principle of least astonishment: historically, this has been the most common way for creating files.

1
  • 1
    This behavior is especially useful when there is a typo in a directory name. Commented Dec 14, 2017 at 13:46
1

For most modern operating systems, directories and files have a set of permissions (and other details) that determine which operations can be performed on the file or directory by who/what. When a file is created, (depending on API) either you explicitly specify the permissions you want, or you get "generic defaults" (that are probably less ideal/less appropriate). This isn't too much of a problem because the process that creates the file is likely to be the only process that's really effected by the resulting "carelessly under-specified" permissions.

When a directory is created the permissions effect everything put in that directory by any person or any process. It's not something that should ever use a generic default from one process because it's a lot more likely that it'll effect more than just that one process.

Don't forget that directory auto-creation would need to be recursive. For example, if you wanted to create the file "/foo/bar/baz/hello.txt" then it may have need to auto-create "/foo" and "/foo/bar" and "/foo/bar/baz".

1
  • Upvoted the answer because it provides an actual implementation issue as a plausible reason: which permissions to give all those nested directories? Commented Dec 14, 2017 at 14:24

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.