Since I keep seeing single long functions, I would like to show the advantages of splitting them into several smaller procedures with an example.
Objective: Splitting a Long String into Multiple Lines
(see https://www.access-programmers.co.u...ing-a-long-string-into-multiple-lines.327492/)
First of all: I don't want to criticise the solutions in the mentioned thread. However, the example fits very well to describe a procedure that (in my opinion) produces easily maintainable code.
Step 1: Solution/idea in short text form
1. Split a string into individual words
2. combine the words into lines whose length must not exceed a certain value.
3. (Extension) Allow splitting of words (condition: each part must be at least N characters long).
Step 2: code structure (no working procedures)
This is where the logic of the code is designed.
Step 3:
fill procedure body bottom up
3a) create test for TrySplitWord and fill code in proc.
=> result:
3b) create test for TryAppendWordToLine and fill code in proc.
=> result:
3c) ... and so on
This approach makes it easier to avoid logic problems.
In addition, I then only have to think about one task when programming and not about several tasks at the same time.
Other advantages:
The individual functions are potentially reusable (TrySplitWord is already used several times in this example).
Debugging becomes easier due to the clearer testability.
A few keywords:
* Single-responsibility principle
* DRY (don't repeat yourself)
What is the argument for a single longer procedure that produces the same result as ConvertTextIntoTextLines?
Objective: Splitting a Long String into Multiple Lines
(see https://www.access-programmers.co.u...ing-a-long-string-into-multiple-lines.327492/)
First of all: I don't want to criticise the solutions in the mentioned thread. However, the example fits very well to describe a procedure that (in my opinion) produces easily maintainable code.
Step 1: Solution/idea in short text form
1. Split a string into individual words
2. combine the words into lines whose length must not exceed a certain value.
3. (Extension) Allow splitting of words (condition: each part must be at least N characters long).
Step 2: code structure (no working procedures)
This is where the logic of the code is designed.
Code:
' Target interface:
Public Function ConvertTextIntoTextLines(ByVal Text2Convert As String, ByVal MaxLineLen As Long, _
Optional ByVal SplitWordMinPartLen As Long = 0) As String
Dim Lines() As String
Lines = SplitTextIntoLines(Text2Convert, MaxLineLen, SplitWordMinPartLen)
ConvertTextIntoTextLines = Join(Lines, vbNewLine)
End Function
Public Function SplitTextIntoLines(ByVal Text2Convert As String, ByVal MaxLen As Long, _
Optional ByVal SplitWordMinPartLen As Long = 0) As String()
' 1. Split Text to words
' Words() = Split(Text2Convert, " ")
' 2. loop lines (merge words to line inside loop) => func GetNextLine
' Do While ..
' Lines(i) = GetNextLine(Words)
' Loop
End Function
Private Function GetNextLine(ByRef Words() As String) As String
' merge words to line until lenght > Maxlen => func TryAppendWordToLine
End Function
Private Function TryAppendWordToLine() As Boolean
' Check line len
' + try split word => func TrySplitWord
End Function
Public Function TrySplitWord(ByVal Word2Split As String, _
ByVal MaxLeftLen As Long, _
ByVal SplitWordMinPartLen As Long, _
ByRef WordParts() As String) As Boolean
' Split word ... fill MaxLeftLen
' condition: len(left side) >= SplitWordMinPartLen and len(right side) >= SplitWordMinPartLen
' return left and right side in array WordParts
End Function
Step 3:
fill procedure body bottom up
3a) create test for TrySplitWord and fill code in proc.
Code:
'---------------------
' Test: TrySplitWord
'
' Test params for row test:
'AccUnit:Row("ThisIsALongText", 5, 2, True, "This-", "IsALongText").Name = "(5/2)This-IsALongText"
'AccUnit:Row("ShortText", 5, 2, True, "Shor-", "tText").Name = "(5/2)Shor-tText"
'AccUnit:Row("ShortText", 5, 4, True, "Shor-", "tText").Name = "(5/4)Shor-tText"
'AccUnit:Row("ShortText", 6, 4, True, "Short-", "Text").Name = "(6/4)Short-Text"
'AccUnit:Row("ShortText", 6, 5, False, "", "").Name = "(6/5)no split"
'AccUnit:Row("ShortText", 5, 5, False, "", "").Name = "(5/5)no split"
'AccUnit:Row("String", 4, 3, True, "Str-", "ing").Name = "(5/3)Str-ing"
'AccUnit:Row("String", 3, 3, False, "", "").Name = "(3/3)no split"
'AccUnit:Row("String", 5, 3, True, "Str-", "ing").Name = "(5/3)Str-ing"
Public Sub Test_TrySplitWord(ByVal Word2Split As String, ByVal MaxLeftLen As Long, ByVal SplitWordMinPartLen As Long, _
ByVal ExpectedWordSplitted As Boolean, ByVal ExpectedLeft As String, ByVal ExpectedRight As String)
Dim WordParts() As String
Dim WordSplitted As Boolean
WordSplitted = TrySplitWord(Word2Split, MaxLeftLen, SplitWordMinPartLen, WordParts())
Assert.That WordSplitted, Iz.EqualTo(ExpectedWordSplitted)
If WordSplitted Then
Assert.That WordParts(0), Iz.EqualTo(ExpectedLeft)
Assert.That WordParts(1), Iz.EqualTo(ExpectedRight)
End If
End Sub
Code:
Public Function TrySplitWord(ByVal Word2Split As String, ByVal MaxLeftLen As Long, ByVal SplitWordMinPartLen As Long, ByRef WordParts() As String) As Boolean
'?: How could proper hyphenation be implemented?
' ... With a .net com lib?
Dim WordSplitted As Boolean
Dim WordSplitPos As Long
ReDim WordParts(2)
If MaxLeftLen <= SplitWordMinPartLen Then
TrySplitWord = False
Exit Function
ElseIf Len(Word2Split) < 2 * SplitWordMinPartLen Then
TrySplitWord = False
Exit Function
End If
If Len(Word2Split) - (MaxLeftLen - 1) >= SplitWordMinPartLen Then
WordSplitPos = MaxLeftLen - 1
ElseIf MaxLeftLen - Len(Word2Split) - SplitWordMinPartLen - 1 >= 0 Then
WordSplitPos = Len(Word2Split) - SplitWordMinPartLen
ElseIf MaxLeftLen - Len(Word2Split) \ 2 - 1 > 0 Then
WordSplitPos = Len(Word2Split) \ 2
End If
If WordSplitPos > 0 Then
WordParts(0) = Left(Word2Split, WordSplitPos) & "-"
WordParts(1) = Mid(Word2Split, WordSplitPos + 1)
WordSplitted = True
End If
TrySplitWord = WordSplitted
End Function
3b) create test for TryAppendWordToLine and fill code in proc.
Code:
'AccUnit:Row("abc", "defg", 7, 2, "abc de-").Name = "(7/2)abc de-"
'AccUnit:Row("abc", "defg", 6, 2, "abc").Name = "(6/2)abc"
'AccUnit:Row("abc", "defg", 6, 1, "abc d-").Name = "(6/2)abc d-"
Public Sub Test_TryAppendWordToLine(ByVal Line As String, ByRef WordToAppend As String, _
ByVal MaxLineLen As Long, ByVal SplitWordMinPartLen As Long, _
ByVal ExpectedLine As String)
TryAppendWordToLine Line, WordToAppend, MaxLineLen, SplitWordMinPartLen
Assert.That Line, Iz.EqualTo(ExpectedLine)
End Sub
Code:
Public Function TryAppendWordToLine(ByRef Line As String, ByRef WordToAppend As String, ByVal MaxLineLen As Long, ByVal SplitWordMinPartLen As Long) As Boolean
Dim WordFullyAppended As Boolean
Dim WordParts() As String
If Len(Line & " " & WordToAppend) > MaxLineLen Then
If SplitWordMinPartLen > 0 Then
If TrySplitWord(WordToAppend, MaxLineLen - Len(Line) - 1, SplitWordMinPartLen, WordParts) Then
Line = Line & " " & WordParts(0)
WordToAppend = WordParts(1)
End If
End If
WordFullyAppended = False
Else
Line = Line & " " & WordToAppend
WordFullyAppended = True
End If
TryAppendWordToLine = WordFullyAppended
End Function
3c) ... and so on
This approach makes it easier to avoid logic problems.
In addition, I then only have to think about one task when programming and not about several tasks at the same time.

Other advantages:
The individual functions are potentially reusable (TrySplitWord is already used several times in this example).
Debugging becomes easier due to the clearer testability.
A few keywords:
* Single-responsibility principle
* DRY (don't repeat yourself)
What is the argument for a single longer procedure that produces the same result as ConvertTextIntoTextLines?
Attachments
Last edited: