Skip to end of metadata
Go to start of metadata

Make it readable

The coding style guidelines on this site are intended to increase the clarity and readability of your FileMaker code. Careful consideration has been given to the fact that it's all too easy to simply "code-n-go" within FileMaker's calculation dialog box. The information on this site is not here as an imposition. It offers a structure which provides liberation from future mental processing.

As of current, FileMaker does not provide a "code editor" inclusive of beneficial features such as syntax highlighting, code completion, block disclosure (for hiding segments of code) and code checking using a linter. Therefore, it is up to the developer to compose clean code which can easily be read not only by the primary developer but anyone else who may need to make sense of what was done.

The current tools available to FileMaker developers include external editors and snippet or keyword expansion tools.

The following is a very simple example of unreadable vs. readable code. This is a very common example of a FileMaker calculation used to provide user feedback such as a message - either within the user interface or some form of output. The goal is to identify if the current customer is tagged within the category of "prospect" and show a message accordingly.

Unreadable code

Let ([ CustomerPhone = Customers::phone ; Categories = List (Customers::Categories) ; Name = Customers::FirstName & " " & Customers::LastName];
If ( PatternCount ( "¶" & Categories & "¶" ; "¶prospect¶" ) ; "Please contact " & Name & " at " & Customers::Phone ; "" ))

Readable code

Let ( [
	~customerName = Interface » Customers::firstName & " " & Interface » Customers::lastName;
	~customerPhone = Interface » Customers::phone;
	~categories = List ( Interface » Customers::categories );
	~categoryMatch = "prospect"
];
	If ( PatternCount ( ¶& ~categories &¶ ; ¶& ~categoryMatch &¶ ) ;
		"Please contact the " & ~categoryMatch & " " & ~customerName & " at " & ~customerPhone
	)
)

Things to note about the code above

  • At a glance, the readable code shows you you're dealing with the customer name, phone number and categories.
  • You can immediately jump to the output "result" portion of the Let() function and actually "read" the output.
  • By putting portions of code on additional lines and indenting, readability increases. The If () function uses the "test" portion on the same line as the function name itself and each of the possible outcomes would appear on their own lines - or multiple if required. In this case, the default is not even required and will only return a result if the test portion evaluates true.
  • The code is written in a DRY fashion. In order to use this same code for a "vendor" instead of a "prospect" the only value to change is ~categoryMatch. The output will update accordingly.
  • The use of the sigil ~ (tilde) to identify the locally scoped Let() variable makes it highly identifiable within the code. Easily distinguished from TableOccurrence, field names, $localVariables and $$GLOBAL.VARIABLES.
  • The space between the semicolon at the end of the If () test portion indicates the continuation of code where the semicolon at the end of the variable declaration ~categories indicates a "break" in visual/mental processing of the code.

All of these types of visual clues contribute to code that is considered "elegant" and easy to read.

Key objectives

  • Encapsulate your code — having to change things in many places is a pain
  • Write highly cohesive code by using strongly named grouping patterns - reuse is the goal
  • Make it DRY — If you see it appear a second time then make it so it will only have to be changed in one place (Don't Repeat Yourself)
  • Avoid tight coupling — i.e. write code with fewer dependencies
  • Handle all possible errors

 

Fixing calculation code

If you currently have messy code and want a head start on code cleanup you can use this tool FileMaker Calculation Formatter. While it does not adhere to the standards outlined here. It does a lot of the hard word for you.

Assumptions

It is assumed that, as a serious FileMaker developer, you are using FileMaker Pro Advanced with access to custom functions and other advanced features. Taking full advantage of FileMaker requires this investment.

  • No labels

9 Comments

  1. One guideline I've advocated in the past that might fit here is this:

    Don't pass commands (to functions and scripts) as parameters. If you want to be able to call on two different operations, write two different functions or scripts.

    For example, the syntax for Tom Robinson's UUID function is "UUID ( type )", where "type" specifies whether to use version 1 (timestamp and MAC address) or version 4 (random numbers) of the RFC 4122 standard. I suggest that this would be better written as two functions, "UUID.v1" and "UUID.v4".

    Here's why. FileMaker's main selling point to developers is that it's faster to work with than just about any other development platform out there. There are several ways FileMaker accomplishes this, but I think the most unique is what I call "the palette of available commands." In an Edit Script window, every script step you can use in a script is listed on the left. In Specify Calculation dialogs, every field, operator, and function you can use in the calculation is listed in the top of the dialog. If you don't know exactly what you're looking for, it's still right there; you don't have to look it up unless the name of a function isn't enough to tell you what it does (which is a separate problem).

    For every function or script that takes a command as a parameter, the name can't be enough to tell you what it does; you have to go look up what the enumerated acceptable values are. That's time I could spend sipping margaritas on the beach. By splitting UUID ( type ) into UUID.v1 and UUID.v4, both options are now visible in the palette of available commands built in to FileMaker.

    One possible exception to this might be commands that take boolean values. The meaning of a "suppressDialogs" parameter for a script is reasonably obvious.

    What does everyone else think?

    1. Anonymous

      I disagree with this, although there may be occasions where 2 versions of the same script may be sensible.

      Why do I disagree? Very simple example. Say I have 3 layouts, 1 for Projects, 1 for Organisations and 1 for Contacts, all based on a globals table with the data for a record being shown by a value in a id_selectedXXX field. On the Organisation and Contacts layout there is a portal to Projects relating to that Organisation or Client. I have one script that goes to the Project, I pass to it the id of the enquiry in the portal. Much the simplest and portable method.

      1. Anonymous

        Sorry, can't log in for some reason so my name missing

        Tim Anderson

      2. Passing the value in id_selectedXXX wouldn't constitute passing a command as a parameter. The id is data, which makes perfect sense to pass as a parameter. What I'm proposing is that we should avoid writing scripts that look like this:

        I don't see how the example you propose behaves like this. Am I missing something?

        1. Anonymous

          Sorry Jeremy, I misunderstood your original post.

          Bur I still disagree, unless I still misunderstand. What about the scenario of using a handler script to display a dialog. One of the parameter could specify whether to use FileMaker's native Custom Dialog, a layout or a plugin.

          Would this scenario comply with your idea?

          Tim Anderson

          1. I can see where Jeremy is heading with this. He is simply promoting a more obvious (blatant, simple - pick a term) method to defining both custom functions and scripts.

            Rather than one Show Dialog script, which would handle the various possibilities of filemaker vs. troi dialog vs. custom dialog layout - all within one script. He's promoting Show FileMaker Dialog vs. Show Troi Dialog vs. Show Layout Dialog scripts. This mirrors FileMaker's obviousness of function names.

            At least that it what it seems like he's promoting.

            To me, this seems like more of a team decision based on programming style. Personally, I favor the embedding of features within one object (e.g. Show Dialog with an inbound "method" parameter).

            To back up Jeremy's argument, yes, this does add complexity in the sense that you have to "learn" or know what the possible options are within your enumerations.

            However, the argument against, is that you'll always have the same logic within the app, whether it's spread across multiple scripts (or functions) vs. one single script. Having to "learn" and "remember" the possible options is a team (or single developer) responsibility - which I'm ok with. From the viewpoint of a single developer, it's much easier because the wrapper is already known - you created it. However, from the vantage point of a team, it's harder because it's not obvious - you have to go into the function or script to see what it requires. This is where easily accessed internal documentation bridges the gap between the two methods - I suggest using a web viewer which points to a development wiki (like this one being used here).

            Also, another argument for the single script approach is that you have a single point of control. If you have on single reference to a Show Dialog within hundreds of other scripts (because it's a common enough feature) then you only need to change that one script rather than having to visit the hundreds of scripts to change what they all call.

            I can see arguments for both approaches and I would suggest that we start a page that outlines the pros and cons of each approach. I personally use both and it typically comes down to how the solution is being crafted. It's not so much a standard as much as a circumstantial situation which determines your angle of attack.

          2. Matt's got the idea right. I first started writing scripts like this in reaction to having to work on a system coded exactly how Matt describes, where scripts handle methods passed as parameters. I could've done my work in half the time (at most!) if not for the fact that using method parameters obscured the functionality of the script. Code has legs, and will inevitably get worked on by someone who isn't part of your team — that developer matters, too. Matt, your choice of words also reveals that you're thinking of scripting in object oriented terms. (So did the developers who built that system I mentioned.) Object oriented programming is a great source of inspiration for how to structure our work, but FileMaker is not object oriented. And a method is the better analogy for a FileMaker script than an object anyway.

            As for the possibility of repeating code, that is a potential issue, to which I have this to say:

            1. Delegate common functionality to a shared sub-script.

            2. Suck it up, princesses. It's not about you. It's about helping future developers who just have to get stuff done, but don't have the time to familiarize themselves with our conventions and paradigms.

            I should note that there's a separate proposal page where this discussion may be better located; and that it's proposed as a best practice, not as a standard (which I neglected to write when I first made that page – oops!).

  2. Anonymous

    Since it's the first reference in the wiki (at least, if you're reading it sequentially), "DRY" should be expanded here to show that in means "Don't Repeat Yourself."    On a similar vein, I'm not sure what is meant by "tight coupling."  Could one of you expand on that a bit for those of us that are newer developers?

    1. Anonymous

      Tight coupling simply means that two or more scripts cannot exist without each other. This is a bad way of developping.

      Let say you develop a Filemaker solution that needs a second solution. The second solution also needs the first one. These are tightly coupled solutions.

      Usually novice programmers (students) have a lot of problem with this concept.

      Nicolas Bourré