Code formatting question

Hey Everybody,

We're trying to codify our formatting for calculations and scripts in my office. Get everyone using the same system. I was hoping to reach out and just poll the community and get some feed back. Here's an example of Let using one formatting proposal for your critiquing. Thanks!

Let([ ~CoCategory = Assets::Category 
	; ~CompSoft = "Software¶Workstations¶Un-Used Computers¶Servers"
	; ~OffEquip = "Camera¶Printers¶Hardware¶Monitors"
	; ~Furn = "Furniture"

	; ~CityCategory = Case	(not IsEmpty ( FilterValues ( ~CompSoft ; ~CoCategory ) )
								; "Computer & Software" 
							; not IsEmpty ( FilterValues ( ~OffEquip ; ~CoCategory ) ) 
								; "Office Equipment" 
							; not IsEmpty ( FilterValues ( ~Furn ; ~CoCategory ) )
								; "Furniture, Fixtures & Leaseholds"
							; "Error: Missing Category"
					  )
	]; 
	~CityCategory  
)

I imagine you'll get as many opinions as respondents to questions like this. But that looks pretty great to me.

My personal preference is:

Let([
	~coCategory = Assets::Category ;
	~compSoft = "Software¶Workstations¶Un-Used Computers¶Servers" ;
	~offEquip = "Camera¶Printers¶Hardware¶Monitors" ;
	~furn = "Furniture" ;

	~cityCategory = Case (
		not IsEmpty ( FilterValues ( ~compSoft ; ~coCategory ) ) ;
			"Computer & Software" ;
		not IsEmpty ( FilterValues ( ~offEquip ; ~coCategory ) ) ;
			"Office Equipment" ;
		not IsEmpty ( FilterValues ( ~furn ; ~coCategory ) ) ;
			"Furniture, Fixtures & Leaseholds" ;
		// else
			"Error: Missing Category"
	)
];
	~cityCategory
)
3 Likes

My version would have these differences:

  • using 2 spaces instead of Tabs for indentation
  • not using "~" for variable names. Question: why is this a thing? After the "fun" I had debugging the problem of using $ in a calc field variable, I would argue "don't use any punctuation at all" especially since ~ and $ are visually a little bit similar.
  • no space before leading parenthesis
  • all Case clauses on a single line
  • no space before opening "(" in a function call, but spaces are OK to separate function parameters).
  • no space before trailing semicolon (though I don't feel strongly about this)
  • I would replace the not IsEmpty() function with a ≠ "" comparison, since I think double-negatives are hard to reason about, and I think it's fun to type unicode characters in code, though I regret it later when I try to type ≠ in some other language... :sob:
  • add a comment with hints and todos and some history

Here's how it would look in my style

/* Convert a specific asset category to a parent category. 
Returns "Error:..." if no match

TODO: these values should not be hard-coded here, but rather should
live in a related table, lookup, or value list for a more data-driven design.

History:
20230101 XMZ created, because Bob needed this for client X
20230201 SBF fixed the bug where Poodles were showing up as "Office Eqiupment"

*/

Let(
[
  coCategory = Assets::Category;
  compSoft = "Software¶Workstations¶Un-Used Computers¶Servers";
  offEquip = "Camera¶Printers¶Hardware¶Monitors";
  furn = "Furniture";

  cityCategory = Case(
    FilterValues(compSoft ; coCategory) ≠ "" ;  "Computer & Software";
    FilterValues(offEquip ; coCategory) ≠ "" ;  "Office Equipment";
    FilterValues(furn ;     coCategory) ≠ "" ;  "Furniture, Fixtures & Leaseholds";
    "Error: Missing Category"
  )
] ;
 cityCategory
)

Edit: many edits as I thought of more ideas.

1 Like

This one is the one I use, with the the small difference that I use ­­~ at the first character of my variables' names. On macOS it's much easier to write lets this way, because on Windows using Tab to indent produces a much wider space, that is set to 7 spaces, and no way to change that. So on Windows must use spaces if you prefer smaller indentation.

Oh… naming conventions and coding styles are like politics. Talking about them brings up so many opinions and emotions.

The let statement in the original post would look like this at my work

Let (
	[
		category__t = Assets::category__tl;
		computerSoftwareList__t = "Software¶Workstations¶Un-Used Computers¶Servers";
		officeEquipmentList__t = "Camera¶Printers¶Hardware¶Monitors";
		furnitureList__t = "Furniture";
	];
	
	Case (
		not IsEmpty (FilterValues (computerSoftwareList__t; category__t));
		"Computer & Software";
		
		not IsEmpty (FilterValues (officeEquipmentList__t; category__t)); 
		"Office Equipment";
		
		not IsEmpty (FilterValues (furnitureList__t; category__t));
		"Furniture, Fixtures & Leaseholds";
		
		"Error: Missing Category"
	)
)

The naming convention is quite restrictive. Characters are limited to a-z, A-Z, 0-9 and the underscore character (_). That's it! Variable and field names use lower camelcase formatting and contain metadata. The metadata indicates value type, field type and storage type. Variable / field name and metadata are separated by a double-underscore. Short forms in names are avoided most of the time. They are documented in comments when used. Keywords are used when appropriate. List is one such keyword.

As for style… all but the simplest code blocks are indented. Blank lines are added for legibility.

Some of the rationale for all of this:

  • avoiding incompatibilities, especially with integration;
  • allowing the use of reserved words;
  • minimizing the need to use escape sequences;
  • having useful troubleshooting information;
  • ease of decoding, especially by new, outside or foreign resources.

I will only say this about comments: they should be generous.

Hope this helps.

1 Like

I pretty much find all the examples by @jwilling , @xochi, and @bdbd immediately readable without even moment of confusion. My own style would be something of a blend of what @jwilling and @bdbd posted, with some small differences here and there. I think that explains why I find theirs so easy to read.

The only example above that I stumble a bit with is the original post that uses the leading semi-colons, as I am not as used to reading code with the leading semi-colon approach, so, admittedly, I stumble a bit with it. The indentation in that first example is different from anything that I do, but I still find it rather readable.

One variation I often do that I don't ever really see in anyone else's FMP calculation style is that I like to call out variables that I am treating as constants by formatting them using a leading underscore and upper case. Since I am the only one I know who does that, I don't recommend it to others, as it will probably win zero points for helping anyone else read the code, but it was so ingrained in me from a long-ago workplace that I still hang onto it.

As an example, some of those first lines might look like this in my code, depending on my intent:

Let([

    ~co_category = Assets::Category ;

    _COMPUTER_SOFTWARE = "Software¶Workstations¶Un-Used Computers¶Servers" ;
    _OFFICE_EQUIPMENT = "Camera¶Printers¶Hardware¶Monitors" ;
    _FURNITURE = "Furniture" ;

Or even in some cases:

Let([

    ~co_category = Assets::Category ;

    _COMPUTER_SOFTWARE = List( "Software" ; "Workstations" ; "Un-Used Computers" ; "Servers" );
    _OFFICE_EQUIPMENT = List( "Camera" ; "Printers" ; "Hardware" ; "Monitors" );
    _FURNITURE = "Furniture" ;

Hard for me to delineate when I'd opt to break the CR-delimited strings up using List, but I will do that sometimes -- but not always.

3 Likes

I like to have things clean, so I use more indents. Also I use the variable "Result" as result so I can easily change the Result in the Data viewer for debugging. And I don't like the Tilde "~". It's just ugly. Plus I don't start lines with a semicolon because it makes it harder to read. This is how it would look like in my case:

Let([ 
	CoCategory = 	Assets::Category; 
	CompSoft = 		"Software¶Workstations¶Un-Used Computers¶Servers"; 
	OffEquip = 		"Camera¶Printers¶Hardware¶Monitors"; 
	Furn = 			"Furniture"; 
	CityCategory = 	Case(
						not IsEmpty ( FilterValues ( CompSoft ; CoCategory ) ); "Computer & Software"; 
						not IsEmpty ( FilterValues ( OffEquip ; CoCategory ) ); "Office Equipment"; 
						not IsEmpty ( FilterValues ( Furn ; CoCategory ) ); "Furniture, Fixtures & Leaseholds"; 
						"Error: Missing Category"
					  	);
	Result =		CityCategory
];
 
Result
  
)
1 Like
Let ( [ 
	  coCategory = Assets::Category 
	; compSoft = List ( "Software" ; "Workstations" ; "Un-Used Computers" ; "Servers" )
	; offEquip = List ( "Camera" ; "Printers" ; "Hardware" ; "Monitors" )
	; furn = "Furniture"

	; cityCategory = 
		Case (
			not IsEmpty ( FilterValues ( compSoft ; coCategory ) )
			; "Computer & Software" 
			; 
			not IsEmpty ( FilterValues ( offEquip ; coCategory ) ) 
			; "Office Equipment" 
			; 
			not IsEmpty ( FilterValues ( furn ; coCategory ) )
			; "Furniture, Fixtures & Leaseholds"
			; 
			"Error: Missing Category"
			)
	]; 
	
		cityCategory 
		 
	)

As always, development standards and best practices make for interesting conversation. I see and appreciate choices others have made, including the well-explained reasons for those choices. They are all very legible.
I think that some very complex/long calculations could impact the practices chosen.
Here are my reasons for the choices made in my example above:

  • Generously use line breaks and whitespace for legibility. One of the most common problems when trying to understand a complex calculation is knowing what LEVEL you are at. Which "parent" function/item does a parameter/sub-function belong to? See, for example, that the variable cityCategory above has a somewhat complex calculation (a Case statement), so just line-break and indent the whole thing. Another reason for this is that FileMaker calculation (Edit Expression) windows always WRAP text, so you really want to avoid that, if possible, by breaking things up clearly yourself. We have no control over how FM will auto-wrap long lines (other than making the window bigger), which can make code hard to read if you keep long lines.
  • Leading semi-colons - in FileMaker calculations, unlike C-style languages, semi-colons do not indicate "code line ending." Instead, they indicate "here comes another parameter." With that syntactical meaning, putting them at the beginning of the line makes sense, especially when there are many. This practice also means, on the Mac, you can triple-click, copy, paste, and paste again to get a new line just like the one you copied, especially useful when adding another line at the end of a Let statement's variable-assignment section. No need to add a semi-colon to the previous "final" line now that there is a new line after it.
  • Let variable names begin with lower-case, no special symbol - this distinguishes them from FunctionNames (initial capital), and retains ability to double-click a variable to select/copy it. With a non-alphanum character/symbol at the beginning, that isn't possible.
  • Do not use the pilcrow (¶) character within a string. I've found that some third-party analysis tools can have problems with this high-ASCII character. So, I usually use Char ( 13 ) within a calculation when I need a single carriage return (ASCII 10, ¶) character. When I want a return-delimited list, I use the List function. I might rarely use them to just stick in a big block of text, instead of having to do "Text1" & Char ( 13 ) & "Text2".
  • Put a space around calculation operators/punctuation - FileMaker's own function templates and code-completion does this. It is easier to read. When you develop in Windows (I rarely do, but sometimes must), only a SPACE character counts as a break for double-click-to-select-text.
  • Case statements - structure them so that a Test is a line that begins with no semi-colon. The next line at the same indentation level that begins with a semi-colon is the Result for that Test. Lines that are ONLY a semi-colon separate between Test/Result groups. If a Test or Result is more than a single short line, add line-breaks and SUB-indent it. The resulting code will mean you can ALWAYS answer "is this a Test, a Result, and which Test/Result does it go with?"
  • Closing parenthesis - just like the lead semi-colons, they are indented one level further than the opening function line. That way, every line indented at the same level are peers, not parts of the previous peer.
6 Likes

Termination at the beginning -- odd -- but handy for commenting out. And not a habit I would bring over to js, CSS etc.

'~' on variables is visually messy but is really handy if you use the MonkeyBread plugin, which offers type-ahead variables.

I have to say: Even though I find the leading semi-colon difficult to read, the above arguably makes sense to me. Thanks @DanShockley , for mentioning it.

2 Likes

I handle the semicolons almost the same way as @DanShockley. For the same reasons. I just put them at the very beginning of the line - without indentation. That way they don't interfere with readability.
For calculation scope variables, I've always used the tilde ~, but now I find the idea of using ¢ for these variables very charming. With the tilde, you have to be careful to write a space before certain letters. With ¢ this is not necessary.

1 Like

@mipiano Can you elaborate on With the tilde, you have to be careful to write a space before certain letters. With ¢ this is not necessary.?

@OregonDean
The tilde is actually an accent. This can occur, for example, with a, o or n.
If you type ~ followed by a, o or n, you get ã, õ, ñ.
If you type a space after ~ and then a, o or n, it works: ~a, ~o, ~n.

Another problem: Let's assume you want to swap two variables: You double-click the variable ~myVar to copy the string. Only the string myVar is copied. Now you double-click the variable ~anotherVar to be replaced and paste the copied string. What is the result? Now it says ~ myVar with a space between the tilde and the variable name.

All this can be quite annoying. Especially if you don't notice it right away and wonder why e.g. a custom function doesn't work.

With a currency sign ($, ¢, €) all this does not happen! With that in mind I just find ¢ suitable for "Calculation“-scoped variables.

1 Like

plus: the tilde is not without meaning in FM since in path notations it as well as in operating systems is the placeholder or shortcut to the users home directory - thus I also avoid using it for naming variables
just two more ¢

1 Like

This is the reason that I don't use the tilde. It already has a specific meaning for me. I understand that many people want the clarity that a prefixed marker gives. Additionally, the use of a prefix allows common and reserved words to be available as variable names.

However, most calculations are so simple that they can be read at a glance. If I do have a preference it is to avoid typing, so I will use variables names that are identical to the placeholder text in functions, like this:

Let ( [ text = "The string";  searchString = "r" ; start = 1 ; occurrence = 1 ] ;  Position ( text ; searchString ; start ; occurrence ) )
1 Like

In retrospect, the tilde is not an ideal variable prefix. I think it just became a convention in the earlier days of FM calc standards discussions, and has stuck around. Underscore would probably get my vote if rewriting history. I do like having a prefix in calcs because it enables MBS autocomplete and variable colorization, which I cannot live without anymore. The prefix also allows you to use otherwise reserved words like list as var names.

EDIT: although, leading underscores also have special conventional meaning in other paradigms, usually denoting a private or unused variable or property... :thinking:

Would you mind giving some details about what is less than ideal for you about the tilde as a variable prefix?

1 Like

Well FWIW, I still use ~, mostly out of habit but especially now that it plays so well with MBS. So it's never been problematic for me. But others pointed out some problems:

I tend to agree that ~ is a little messy. To my eye, even moreso compared to _, which is a little cluttered itself. But I won't likely be switching away from ~ for the time being.

4 Likes

I like the arguments for ; at the beginning of lines, but I just… can't. :rofl:

I admit it's not logical, and some thing like copying and commenting out single lines would be easier≤ but it just breaks my brain to see them at the beginning.

I haven't run into the ~ automatically becoming an accent character. Is that a Windows thing? That would drive me batty. But I've never liked dealing with special chars in Windows. I'm a MacGeek all the way!

5 Likes

@flybynight
No, for once Windows is innocent of the "TildeBecomesAccent" problem. :rofl: I'm a MacGeek too. And I run often into that situation.