Apr 21, 2020 Replies: 43 "Code smells" are common patterns that can indicate that there is a problem in the code. A problem doesn't mean that the code doesn't work, it might be that it will be difficult to maintain it or that there are more elegant ways to implement the same thing. Remember that not everything is clear cut and there are exceptions for any rule. I will update this list whenever I encounter such smell Initializing an object and then assigning a different object to the same variable: Code: 'bad Dim List1 As List List1.Initialize '<-- a new list was created here List1 = SomeOtherList '<--- previous list was replaced 'good Dim List1 As List = SomeOtherList Deprecated methods - DoEvents, Msgbox: https://www.b4x.com/android/forum/t...cated-and-async-dialogs-msgbox.79578/#content Deprecated methods - Map.GetKeyAt / GetValueAt - these methods were added before the For Each loop was available. They are not cross platform and are not the correct way to work with maps. Code: 'bad For i = 0 To Map1.Size - 1 Dim key As String = Map1.GetKeyAt(i) Dim value As String = Map1.GetValueAt(i) Next 'good For Each key As String In Map1.Keys Dim value As String = Map1.Get(key) Next File.DirDefaultExternal - This is always a mistake. In most cases the correct folder should be XUI.DefaultFolder (=File.DirInternal). If you do need to use the external storage then use RuntimePermissions.GetSafeDirDefaultExternal. File.DirRootExternal - It will soon become inaccessible directly. If really needed then use ContentChooser or ExternalStorage. Not using parameterized queries: Code: 'very bad SQL.ExecNonQuery("INSERT INTO table1 VALUES ('" & EditText1.Text & "'") 'ugly, will break if there is an apostrophe in the text and vulnerable to SQL injections. 'very good SQL.ExecNonQuery2("INSERT INTO table1 VALUES (?)", Array(EditText1.Text)) Using Cursor instead of ResultSet - Cursor is a B4A only object. ResultSet is a bit simpler to use and is cross platform. Code: 'good Dim rs As ResultSet = SQL.ExecQuery2(...) Do While rs.NextRow ... Loop rs.Close Building the complete layout programmatically. This is especially a mistake in B4J and B4i because of the resize event and also if you want to build a cross platform solution. Layouts can be ported very easily. Repeating the code. There are many patterns to this one and all of them are bad. Code: 'bad If b = False Then Button1.Text = "disabled" Button2.Text = "disabled" Button3.Text = "disabled" Button1.Enabled = False Button2.Enabled = False Button3.Enabled = False Else Button1.Text = "enabled" Button2.Text = "enabled" Button3.Text = "enabled" Button1.Enabled = True Button2.Enabled = True Button3.Enabled = True End If Code: 'good For Each btn As Button In Array(Button1, Button2, Button3) btn.Enabled = b If b Then btn.Text = "enabled" Else btn.Text = "disable" Next Long strings without using smart strings: Code: 'bad Dim s As String = "This is the " & QUOTE & "first" & QUOTE & "line" & CRLF & _ "and this is the second one. The time is " & DateTime.Time(DateTime.Now) & "." 'good Dim s As String = $"This is the "first" line and this is the second one. The time is $Time{DateTime.Now}."$ More information: https://www.b4x.com/android/forum/threads/50135/#content Using global variables when not needed: Code: 'bad Job.Initialize(Me, "") 'global variable ... 'good Dim job As HttpJob job.Initialize(Me, "") Not using Wait For when possible. JobDone is a good example: [B4X] OkHttpUtils2 with Wait For Using code modules instead of classes. Code modules are very limited in B4A. In most cases you should use classes instead of code modules. A code module is a single instance of a class. Understanding booleans. Code: 'not elegant Dim result As Boolean = DoingSomethingThatReturnTrueOrFalse If result = True Then Return True Else Return False End If ' elegant Return DoingSomethingThatReturnTrueOrFalse Converting "random" bytes to strings. The only valid raw bytes that should be converted to a string, with BytesToString, are bytes that represent text. In all other cases it is a mistake to convert to string. Even if it seems to work it will later fail in other cases. If you think that it is more complicated to work with raw bytes then you are not familiar with the useful B4XBytesBuilder object: https://www.b4x.com/android/forum/threads/b4x-b4xcollections-more-collections.101071/#content Generating or parsing XML / JSON by hand. These formats are far from being trivial and with all kinds of edge cases that no one remembers. Code: 'bad Dim s As String = "{""version"":""" & Version & """,""colors"":[""red"",""green"",""blue""]}" 'good Dim jg As JSONGenerator jg.Initialize(CreateMap("colors": Array("red", "green", "blue"), "version": Version)) Log(jg.ToPrettyString(4)) Assuming that Maps preserve order. (Hash)Maps, unlike Lists or arrays, in the general case do not preserve order. It is inherent to the way hash maps work and true in all programming languages. There are some exceptions in B4X which are: - B4A and B4J regular maps do preserve order. - B4XOrderedMap from B4XCollections is built as a List + Map and it preserves order. It actually allows sorting the keys, which can be useful in some cases. A common case of confusion is JSON object(=Map) parsing and generation. The JSON specs specifically "does not assign any significance to the ordering of name/value pairs". Related thread: https://www.b4x.com/android/forum/threads/b4x-features-that-erel-recommends-to-avoid.133280/ That's it for now. Last edited: Sep 6, 2023 Click to expand...
"Code smells" are common patterns that can indicate that there is a problem in the code. A problem doesn't mean that the code doesn't work, it might be that it will be difficult to maintain it or that there are more elegant ways to implement the same thing. Remember that not everything is clear cut and there are exceptions for any rule. I will update this list whenever I encounter such smell Initializing an object and then assigning a different object to the same variable: Code: 'bad Dim List1 As List List1.Initialize '<-- a new list was created here List1 = SomeOtherList '<--- previous list was replaced 'good Dim List1 As List = SomeOtherList Deprecated methods - DoEvents, Msgbox: https://www.b4x.com/android/forum/t...cated-and-async-dialogs-msgbox.79578/#content Deprecated methods - Map.GetKeyAt / GetValueAt - these methods were added before the For Each loop was available. They are not cross platform and are not the correct way to work with maps. Code: 'bad For i = 0 To Map1.Size - 1 Dim key As String = Map1.GetKeyAt(i) Dim value As String = Map1.GetValueAt(i) Next 'good For Each key As String In Map1.Keys Dim value As String = Map1.Get(key) Next File.DirDefaultExternal - This is always a mistake. In most cases the correct folder should be XUI.DefaultFolder (=File.DirInternal). If you do need to use the external storage then use RuntimePermissions.GetSafeDirDefaultExternal. File.DirRootExternal - It will soon become inaccessible directly. If really needed then use ContentChooser or ExternalStorage. Not using parameterized queries: Code: 'very bad SQL.ExecNonQuery("INSERT INTO table1 VALUES ('" & EditText1.Text & "'") 'ugly, will break if there is an apostrophe in the text and vulnerable to SQL injections. 'very good SQL.ExecNonQuery2("INSERT INTO table1 VALUES (?)", Array(EditText1.Text)) Using Cursor instead of ResultSet - Cursor is a B4A only object. ResultSet is a bit simpler to use and is cross platform. Code: 'good Dim rs As ResultSet = SQL.ExecQuery2(...) Do While rs.NextRow ... Loop rs.Close Building the complete layout programmatically. This is especially a mistake in B4J and B4i because of the resize event and also if you want to build a cross platform solution. Layouts can be ported very easily. Repeating the code. There are many patterns to this one and all of them are bad. Code: 'bad If b = False Then Button1.Text = "disabled" Button2.Text = "disabled" Button3.Text = "disabled" Button1.Enabled = False Button2.Enabled = False Button3.Enabled = False Else Button1.Text = "enabled" Button2.Text = "enabled" Button3.Text = "enabled" Button1.Enabled = True Button2.Enabled = True Button3.Enabled = True End If Code: 'good For Each btn As Button In Array(Button1, Button2, Button3) btn.Enabled = b If b Then btn.Text = "enabled" Else btn.Text = "disable" Next Long strings without using smart strings: Code: 'bad Dim s As String = "This is the " & QUOTE & "first" & QUOTE & "line" & CRLF & _ "and this is the second one. The time is " & DateTime.Time(DateTime.Now) & "." 'good Dim s As String = $"This is the "first" line and this is the second one. The time is $Time{DateTime.Now}."$ More information: https://www.b4x.com/android/forum/threads/50135/#content Using global variables when not needed: Code: 'bad Job.Initialize(Me, "") 'global variable ... 'good Dim job As HttpJob job.Initialize(Me, "") Not using Wait For when possible. JobDone is a good example: [B4X] OkHttpUtils2 with Wait For Using code modules instead of classes. Code modules are very limited in B4A. In most cases you should use classes instead of code modules. A code module is a single instance of a class. Understanding booleans. Code: 'not elegant Dim result As Boolean = DoingSomethingThatReturnTrueOrFalse If result = True Then Return True Else Return False End If ' elegant Return DoingSomethingThatReturnTrueOrFalse Converting "random" bytes to strings. The only valid raw bytes that should be converted to a string, with BytesToString, are bytes that represent text. In all other cases it is a mistake to convert to string. Even if it seems to work it will later fail in other cases. If you think that it is more complicated to work with raw bytes then you are not familiar with the useful B4XBytesBuilder object: https://www.b4x.com/android/forum/threads/b4x-b4xcollections-more-collections.101071/#content Generating or parsing XML / JSON by hand. These formats are far from being trivial and with all kinds of edge cases that no one remembers. Code: 'bad Dim s As String = "{""version"":""" & Version & """,""colors"":[""red"",""green"",""blue""]}" 'good Dim jg As JSONGenerator jg.Initialize(CreateMap("colors": Array("red", "green", "blue"), "version": Version)) Log(jg.ToPrettyString(4)) Assuming that Maps preserve order. (Hash)Maps, unlike Lists or arrays, in the general case do not preserve order. It is inherent to the way hash maps work and true in all programming languages. There are some exceptions in B4X which are: - B4A and B4J regular maps do preserve order. - B4XOrderedMap from B4XCollections is built as a List + Map and it preserves order. It actually allows sorting the keys, which can be useful in some cases. A common case of confusion is JSON object(=Map) parsing and generation. The JSON specs specifically "does not assign any significance to the ordering of name/value pairs". Related thread: https://www.b4x.com/android/forum/threads/b4x-features-that-erel-recommends-to-avoid.133280/ That's it for now.
Alexander Stolte Expert Licensed User Longtime User Apr 21, 2020 #2 Erel said: Using code modules instead of classes. Code modules are very limited in B4A. In most cases you should use classes instead of code modules. A code module is a single instance of a class. Click to expand... you should also write that you can simply initialize the class in the starter service. I have used CodeModules in the past, because initializing the class scared me. I didn't come up with the idea to simply initialize the class in the starter service and then make it public. 12. Not using XUI, many things are easier with the xui library
Erel said: Using code modules instead of classes. Code modules are very limited in B4A. In most cases you should use classes instead of code modules. A code module is a single instance of a class. Click to expand... you should also write that you can simply initialize the class in the starter service. I have used CodeModules in the past, because initializing the class scared me. I didn't come up with the idea to simply initialize the class in the starter service and then make it public. 12. Not using XUI, many things are easier with the xui library
Erel B4X founder Staff member Licensed User Longtime User Apr 21, 2020 #3 Alexander Stolte said: 12. Not using XUI, many things are easier with the xui library Click to expand... I almost always use B4XView for the various views. It is simple, cross platform and also includes a few features not available elsewhere. However at this point it is not a mistake to use the platform specific types.
Alexander Stolte said: 12. Not using XUI, many things are easier with the xui library Click to expand... I almost always use B4XView for the various views. It is simple, cross platform and also includes a few features not available elsewhere. However at this point it is not a mistake to use the platform specific types.
kisoft Well-Known Member Licensed User Longtime User Apr 21, 2020 #4 It's good that you posted this thread. Too often, I use the code from number 7...
Sandman Expert Licensed User Longtime User Apr 21, 2020 #5 Would this be considered a code smell? (It is a big pet peeve of mine when I see an evaluation that return only the value that just was evaluated instead of simply returning it in the first place. Not sure how to explain better, perhaps easier to understand by looking at the code.) Code: ' bad result = doingSomethingThatReturnTrueOrFalse If result = True Then Return True Else Return False End If ' good return doingSomethingThatReturnTrueOrFalse
Would this be considered a code smell? (It is a big pet peeve of mine when I see an evaluation that return only the value that just was evaluated instead of simply returning it in the first place. Not sure how to explain better, perhaps easier to understand by looking at the code.) Code: ' bad result = doingSomethingThatReturnTrueOrFalse If result = True Then Return True Else Return False End If ' good return doingSomethingThatReturnTrueOrFalse
yfleury Active Member Licensed User Longtime User Apr 21, 2020 #6 I learn much with this threat. I can reduce size of my code for sure
incendio Well-Known Member Licensed User Longtime User Apr 22, 2020 #7 Erel said: Not using parameterized query Click to expand... I'm using sql a lots, there was a time when using parameterized query on insert command gave an error, while using traditional method worked fine. I will try to look that command. Last edited by a moderator: Jun 29, 2020
Erel said: Not using parameterized query Click to expand... I'm using sql a lots, there was a time when using parameterized query on insert command gave an error, while using traditional method worked fine. I will try to look that command.
Erel B4X founder Staff member Licensed User Longtime User Apr 22, 2020 #8 Sandman said: Would this be considered a code smell? Click to expand... I will add it.
Erel B4X founder Staff member Licensed User Longtime User Apr 22, 2020 #9 incendio said: here was a time when using parameterized query on insert command gave an error, Click to expand... Not everything can be parameterized. The table name and column names cannot be parameterized, however it is quite rare that you need to "parameterize" these things.
incendio said: here was a time when using parameterized query on insert command gave an error, Click to expand... Not everything can be parameterized. The table name and column names cannot be parameterized, however it is quite rare that you need to "parameterize" these things.
incendio Well-Known Member Licensed User Longtime User Apr 22, 2020 #10 Erel said: Not everything can be parameterized. The table name and column names cannot be parameterized, however it is quite rare that you need to "parameterize" these things. Click to expand... It was parameterized a value, not table name or column names, will post the codes if find it.
Erel said: Not everything can be parameterized. The table name and column names cannot be parameterized, however it is quite rare that you need to "parameterize" these things. Click to expand... It was parameterized a value, not table name or column names, will post the codes if find it.
Erel B4X founder Staff member Licensed User Longtime User Apr 22, 2020 #11 incendio said: It was parameterized a value, not table name or column names, will post the codes if find it. Click to expand... Start a new thread if you think that you encountered such case.
incendio said: It was parameterized a value, not table name or column names, will post the codes if find it. Click to expand... Start a new thread if you think that you encountered such case.
M Mahares Expert Licensed User Longtime User Apr 22, 2020 #12 What do you do if you are interested in a given key at at a given index, do you still iterate over all keys until you find it and then exit the loop, Or you cannot use the key index as the map order changes every time you access the map. Last edited: Apr 25, 2020
What do you do if you are interested in a given key at at a given index, do you still iterate over all keys until you find it and then exit the loop, Or you cannot use the key index as the map order changes every time you access the map.
udg Expert Licensed User Longtime User Apr 22, 2020 #13 Not so much time ago I planned to start a thread in the Wish area of the forum about Best (and eventually Bad) practices. Having Erel just list best practices as of today (and keep it updated) in order to show newcomers (and old timers too) what should generally be done (i.e. valid 80/90% of times) in most of the recurring circumstances would have been of great help for anybody, IMHO. Yes, most if not all the info is already published in the forum and we know that "the search engine works" is a truth. But consider a single thread, closed to comments, where you find "how to" do correctly almost anything..
Not so much time ago I planned to start a thread in the Wish area of the forum about Best (and eventually Bad) practices. Having Erel just list best practices as of today (and keep it updated) in order to show newcomers (and old timers too) what should generally be done (i.e. valid 80/90% of times) in most of the recurring circumstances would have been of great help for anybody, IMHO. Yes, most if not all the info is already published in the forum and we know that "the search engine works" is a truth. But consider a single thread, closed to comments, where you find "how to" do correctly almost anything..
klaus Expert Licensed User Longtime User Apr 22, 2020 #14 The Code Smells have been added in the B4X Basic Language Booklet for the next update.
LucaMs Expert Licensed User Longtime User Apr 22, 2020 #15 udg said: Not so much time ago I planned to start a thread in the Wish area of the forum about Best (and eventually Bad) practices. Having Erel just list best practices as of today (and keep it updated) in order to show newcomers (and old timers too) what should generally be done (i.e. valid 80/90% of times) in most of the recurring circumstances would have been of great help for anybody, IMHO. Yes, most if not all the info is already published in the forum and we know that "the search engine works" is a truth. But consider a single thread, closed to comments, where you find "how to" do correctly almost anything.. Click to expand... "Best practices" and this "Code Smells" are different things, so I think that another new thread for "Best practices" would be useful.
udg said: Not so much time ago I planned to start a thread in the Wish area of the forum about Best (and eventually Bad) practices. Having Erel just list best practices as of today (and keep it updated) in order to show newcomers (and old timers too) what should generally be done (i.e. valid 80/90% of times) in most of the recurring circumstances would have been of great help for anybody, IMHO. Yes, most if not all the info is already published in the forum and we know that "the search engine works" is a truth. But consider a single thread, closed to comments, where you find "how to" do correctly almost anything.. Click to expand... "Best practices" and this "Code Smells" are different things, so I think that another new thread for "Best practices" would be useful.
Erel B4X founder Staff member Licensed User Longtime User Apr 22, 2020 #16 Mahares said: What do you do if you are interested in a given key at at a given index, do you still iterate over all keys until you find it and then exit the loop, Or you cannot use the key index as the map order changes every time you access the map. Click to expand... Map is not built for this task. You should use B4XOrderedMap from B4XCollections.
Mahares said: What do you do if you are interested in a given key at at a given index, do you still iterate over all keys until you find it and then exit the loop, Or you cannot use the key index as the map order changes every time you access the map. Click to expand... Map is not built for this task. You should use B4XOrderedMap from B4XCollections.
S sorex Expert Licensed User Longtime User Apr 22, 2020 #18 will .getKeyAt & .getValueAt remain? in some cases you don't need to go through the entire loop/map but just pick a random item or the first/last one. it can be done with the loop ofcourse by using exit/return but it looks weird to use a loop for that.
will .getKeyAt & .getValueAt remain? in some cases you don't need to go through the entire loop/map but just pick a random item or the first/last one. it can be done with the loop ofcourse by using exit/return but it looks weird to use a loop for that.
Erel B4X founder Staff member Licensed User Longtime User Apr 22, 2020 #19 sorex said: will .getKeyAt & .getValueAt remain? Click to expand... Yes. sorex said: in some cases you don't need to go through the entire loop/map but just pick a random item or the first/last one. Click to expand... See my answer in post #16. Note that the iterator needs to loop internally over the items. You just don't see it. Correct solution for such rare cases is to use B4XOrderedMap. It gives you the keys as a list.
sorex said: will .getKeyAt & .getValueAt remain? Click to expand... Yes. sorex said: in some cases you don't need to go through the entire loop/map but just pick a random item or the first/last one. Click to expand... See my answer in post #16. Note that the iterator needs to loop internally over the items. You just don't see it. Correct solution for such rare cases is to use B4XOrderedMap. It gives you the keys as a list.
Jorge M A Well-Known Member Licensed User Longtime User Apr 23, 2020 #20 Erel said: Correct solution for such rare cases is to use B4XOrderedMap. Click to expand... I'm not sure I understand this. It is common for me to use GetKeyAt(Index) in conjunction with ComboBox. the map keys are not related to the order of the combo list. Agree with @sorex in some cases you don't need to go through the entire loop/map but just pick a random item Click to expand... Is it possible to rectify cbxEngineType_SelectedIndexChangeden in the code? (Project Attached) Thank you. [CODE lang="b4x" highlight="2"]Sub cbxEngineType_SelectedIndexChanged(Index As Int, Value As Object) lblKey.Text=enginesMap.GetKeyAt(Index) End Sub[/CODE] Attachments MyComboMapped.zip MyComboMapped.zip 2.5 KB · Views: 1,669
Erel said: Correct solution for such rare cases is to use B4XOrderedMap. Click to expand... I'm not sure I understand this. It is common for me to use GetKeyAt(Index) in conjunction with ComboBox. the map keys are not related to the order of the combo list. Agree with @sorex in some cases you don't need to go through the entire loop/map but just pick a random item Click to expand... Is it possible to rectify cbxEngineType_SelectedIndexChangeden in the code? (Project Attached) Thank you. [CODE lang="b4x" highlight="2"]Sub cbxEngineType_SelectedIndexChanged(Index As Int, Value As Object) lblKey.Text=enginesMap.GetKeyAt(Index) End Sub[/CODE]