Today, we are going to be talking about some best cases for functional layout and some ways to optimize those functions.
From my school days, there was no real conversation about either best cases or optimizations, so what I am going to be talking through will be mostly my own experiences and what I have gleaned from different books about programming. Most individuals believe that functions should be extremely little (down to a single line of code if possible), while others have a concept that a function should contain every aspect of the code so that they don’t have to write functions that are never going to be used again. Let me give you an example of each.
To do this we will use a single VBScript written two ways, this script opens a text file, and for each line in that text file, each line is a customer and a project name delimited by a pipe, it then converts the spaces to underscores (for easier managing for interfacing with a GitLab) and then ensures that the customer folder exists within the existing Projects Folder and then ensures that the project folder exists within the customer folder. If either of those folders don’t exist, then it will create the folder within that location.
To Many Functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 |
Const BASE_PATH = "C:\Projects\" Const DELIMINATOR = "|" Call Main Sub Main Set File = OpenFile("C:\Projects\CustomerProject.txt") ProcessCustomerProjectFile File MsgBox "Done!", vbOKOnly + vbInformation, "Status" End Sub Function OpenFile(ByVal PathToFile) Set OpenFile = CreateObject("Scripting.FileSystemObject").OpenTextFile(PathToFile) End Function Function ProcessCustomerProjectFile(ByRef File) Do While Not File.AtEndOfStream CustomerProject = File.ReadLine ProcessProjectFolder ProcessCustomerFolder(BASE_PATH, _ CustomerProject), _ CustomerProject Loop End Function Function ProcessCustomerFolder(ByVal BasePath, ByVal CustomerProject) CustomerName = GetCustomerName(CustomerProject) CustomerPath = AddBackSlash(BasePath & ReplaceSpacesWithUnderScores(CustomerName)) CreateFolderIfItDoesNotExist CustomerPath ProcessCustomerFolder = CustomerPath End Function Function GetCustomerName(ByVal CustomerProject) If CustomerProject <> "" Then GetCustomerName = Split(CustomerProject, DELIMINATOR)(0) Else GetCustomerName = "" End If End Function Function ProcessProjectFolder(ByVal BasePath, ByVal CustomerProject) ProjectName = GetProjectName(CustomerProject) ProjectPath = AddBackSlash(BasePath & ReplaceSpacesWithUnderScores(ProjectName)) CreateFolderIfItDoesNotExist ProjectPath ProcessProjectFolder = ProjectPath End Function Function GetProjectName(ByVal CustomerProject) If InStr(1, CustomerProject, DELIMINATOR) > 1 Then GetProjectName = Split(CustomerProject, DELIMINATOR)(1) Else GetProjectName = "" End If End Function Function CreateFolderIfItDoesNotExist(ByVal Path) If Not DoesFolderExist(Path) Then CreateFolder Path End If End Function Function DoesFolderExist(ByVal PathToFolder) DoesFolderExist = CreateObject("Scripting.FileSystemObject").FolderExists(PathToFolder) End Function Function CreateFolder(ByVal FolderToCreate) CreateObject("Scripting.FileSystemObject").CreateFolder(FolderToCreate) End Function Function ReplaceSpacesWithUnderScores(ByVal DataToTest) ReplaceSpacesWithUnderScores = Replace(DataToTest, " ", "_") End Function Function AddBackSlash(ByVal PathToTest) If Right(PathToTest, 1) <> "\" Then AddBackSlash = PathToTest & "\" Else AddBackSlash = PathToTest End If End Function |
Pros
- Each of the functions are incredibly small, in fact the largest function contains 5 lines of code.
- Most of the functions are designed in a manner that would allow for an easy process to create unit tests against.
- Most of the functions are extremely portable, they could be picked up and moved to another project with relative ease.
Cons
- Way to long, there are 86 lines of code for a relatively easy project such as this. Plus, who actually read all 86 lines of code to figure out what was going on?
- Most of the functions are one time wonders, used once within the project and not used again.
- This could run slower… for this particular example, the Scripting.FileSystemObject took no time to instantiate, I have witnessed some objects take anywhere from .01 seconds to even 3 seconds to load into memory so this could become a very long winded event.
To Few Functions
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 |
Const BASE_PATH = "C:\Projects\" Const DELIMINATOR = "|" Call Main Sub Main Set FSO = CreateObject("Scripting.FileSystemObject") Set File = FSO.OpenTextFile("C:\Projects\CustomerProject.txt") Do While Not File.AtEndOfStream CustomerProject = File.ReadLine If CustomerProject <> "" Then CustomerName = Split(CustomerProject, DELIMINATOR)(0) Else CustomerName = "" End If CustomerName = Replace(CustomerName, " ", "_") CustomerPath = BASE_PATH & CustomerName If Right(CustomerPath, 1) <> "\" Then CustomerPath = CustomerPath & "\" End If If Not FSO.FolderExists(CustomerPath) Then FSO.CreateFolder(CustomerPath) End If If InStr(1, CustomerProject, DELIMINATOR) > 1 Then ProjectName = Split(CustomerProject, DELIMINATOR)(1) Else ProjectName = "" End If CustomerName = Replace(ProjectName, " ", "_") ProjectPath = CustomerPath & ProjectName If Right(ProjectPath, 1) <> "\" Then ProjectPath = ProjectPath & "\" End If If Not FSO.FolderExists(ProjectPath) Then FSO.CreateFolder(ProjectPath) End If Loop MsgBox "Done!", vbOKOnly + vbInformation, "Status" End Sub |
Pros
- Should be faster because we only had to instantiate the File System Object once, and then use it multiple times
- There are no functions that are only called once for the entire project. For instance, the first example had a function called ProcessCustomerProjectFile, which was only used once, and not very effectively.
- Everything that is done is done in one spot, no back and forth throughout the code to locate what each function is supposed to do
- No reliance on code that might be altered.
- Shorter, only 50 lines of code
Cons
- Duplicate code. The replace spaces with underscores is used twice, the CreateFolder process is duplicated, etc. This is a ripe environment for one-off errors.
- Impossible to unit test. In order to test it, you have to run the entire thing, if any part of it fails then it becomes a tracing nightmare.
- Hard to read. because everything is happening, when another programmer is coming against this, they will have to read each and every line of code in that function and remember every variable, every transaction, every nuance of events that is going on to understand what this function is expecting to do.
- Variable Span (VS) is large. So the VS is the description of the number of lines between when it is created and the final usage. For instance, FSO is created on line 6, but the last time it is used is line 45, so it’s VS is 39 (Last Line – First Line or 45 – 6). that is a lot of lines of code to have something interact with FSO and break it and also a lot of lines of code that the next developer will have to remember what the acronym FSO means. in the previous example the most that the VS possibly could be was 5. The VS for CustomerProject is 21 (it is created on line 10 and the last instance is on line 31).
- Unable to be re-used. In essence, there isn’t a piece of this code that can easily be picked up and used again in a different area. it is completely dialed into the specific instance of the application.
Summary
Between these two extremes of a small project, it is easy to see where both camps can cause problems and go into the abyss of code analysis nightmare land that I find myself getting stuck in from other developers. To me, there is a smooth in the middle where if there is code that two or more subsequent lines of code that are duplicated, I will refactor that to become a single function that will become self-sufficient. If I find that my VLS is starting to grow long, or my function is starting to get past 30 lines of code (yes, that is just an arbitrary number that I have), I will refactor the code to make it easier to read. If I find that there is a section of code that is “strange” then I will refactor that code to make the function name logical statement of what is expected to happen (and probably document the holy living garbage out of it). After I have refactored it to a point that I am happy with, I will then take a quick once over on performance and see if it is running quickly. I will usually have three tests, a single run test, and medium size test and a what the heck am I thinking test to see if it is hindered. If it is, then I will optimize it. If I start running into questionable items (such as things I am not absolutely certain on), I will refactor that to a separate function so that it can run in a unit test. At this point, I see how Unit Tests can be really awesome utilities that will ensure that if you get what you expect, then it is happy; however, I have had to many “what was the user thinking” inputs that would crash my code that would never be caught within a Unit Test to make Unit Testing my ultimate goal.
Until next time, Happy Coding and may you be blessed!