Promotion of short procedures: Splitting a string into lines (1 Viewer)

Josef P.

Well-known member
Local time
Today, 14:18
Joined
Feb 2, 2023
Messages
826
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.
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
=> result:
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
=> result:
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

  • RE_ConvertTextToTextLines.zip
    30 KB · Views: 79
Last edited:

Users who are viewing this thread

Top Bottom