I found in the catalog of Refactoring by Martin Fowler, with Kent Beck
book that they mention Extract Function refactoring.
- It is a good practice to wrap your related code into local functions to make easy the readability and maintenance of code?
I always thought that local functions were created to avoid writing multiple times the same code inside some particular function that has to repeat some lines. One example can be for example modifying a string value like in the following example.
private void CopyOneFileToOtherInSomeDirectory(string path, string filename, string outputFilename)
{
// Reading document
var reader = File.OpenText($"{AppendPathSeparator(path)}{filename}");
var text = reader.ReadToEnd();
// Writing
using (FileStream fs = File.Create($"{AppendPathSeparator(path)}{outputFilename}"))
{
byte[] textBytes = new UTF8Encoding(true).GetBytes(text);
fs.Write(textBytes, 0, textBytes.Length);
}
string AppendPathSeparator(string filepath)
{
return filepath.EndsWith(@"\") ? filepath : filepath + @"\";
}
}
but in the Extract Function refactoring extracting to local function can be applied to refactor the last example to something like this.
private void CopyOneFileToOtherInSomeDirectory(string path, string filename, string outputFilename)
{
string Read()
{
var reader = File.OpenText($"{AppendPathSeparator(path)}{filename}");
return reader.ReadToEnd();
}
void Write(string text)
{
using (FileStream fs = File.Create($"{AppendPathSeparator(path)}{outputFilename}"))
{
byte[] textBytes = new UTF8Encoding(true).GetBytes(text);
fs.Write(textBytes, 0, textBytes.Length);
}
}
string AppendPathSeparator(string filepath)
{
return filepath.EndsWith(@"\") ? filepath : filepath + @"\";
}
var readedText = Read();
Write(readedText);
}
It looks bigger but with one of these awesome IDE like the IntelliJ ones you can collapse the inner methods and get something really clear to follow.
private void CopyOneFileToOtherInSomeDirectory(string path, string filename, string outputFilename)
{
string Read(){...}
void Write(string text){...}
string AppendPathSeparator(string filepath){...}
var readedText = Read();
Write(readedText);
}
What do you think? is it a good practice to chop the functions with multiple steps to inner functions?
2. Can this have some impact in performance terms?
Read()
a good abstraction vocabulary for what it does? I don't think so. It lacks clarity, as you don't see where it reads from" The question was about the usage of local functions, not which specific signature to use for them. Also, in regards to the improved signature, I'm struggling to see why your advice would then also not apply to actual class methods, which would then always be forced to have their class properties added as explicit method parameters, no? Or is your advice different on the class vs local method scope (i.e. class = allowing hidden inputs, local method = not)? – Flater Mar 15 '21 at 09:49this
instance for local calls). This applies if the necessary parameters can be easily understood to be part of the instance state. E.g. if I find a no-argsReadLine()
method in aStream
class, I get a good idea about what it does, if it appears in aMatrix
class, I'm confused. – Ralf Kleberhoff Mar 15 '21 at 10:27Read()
method inside aCopyOneFileToOtherInSomeDirectory(string path, string filename, ...)
method is similarly easily understood (likeReadLine()
in aStream
class) to rely on the path/filename of the parent method, by virtue of a copy instruction being easily understood as a read-then-write operation. – Flater Mar 15 '21 at 10:30