Complex Scripts

I have found that given the type of work that I do—the technical side of book publishing—that my Applescripts, particularly the ones designed to do automated layouts, are exceedingly long, though not unjustifiably so. 800 lines isn’t out of the ordinary, 1200 is fairly typical, but I have a couple that hit the 2000 line mark. Over the years of banging out these scripts (though they are really applications unto themselves), I have developed a number of techniques, that I have yet to see anywhere else, to help me manage the kind of complexity and length needed to create complex output with as little work as possible for the user. Hopefully, someone else will find this information as useful as I do.

The code example at the end of this article shows all of the principles described here, but I think the explanations will help explain the reasoning behind them all.

Use The Right Tool For The Job

If you know you are going to be doing a lot of Applescripting or starting to do more advanced work, do yourself a favor and buy Script Debugger by Late Night Software. Script Debugger has a lot of programming tools developers have come to expect that Apple’s Script Editor lacks (admittedly, it’s free, but you’re getting what you pay for).

Script Debugger is great because it is as simple to use as Script Editor, but offers a lot more insight into what is happening. Script Editor comes with few tools for proper debugging. In Script Editor there is the Result window, but it only shows one value at a time. Script Debugger’s Result View shows you all of the variables in use and highlights in red the ones that changed. Script Editor has the Log View to log values and see live updates, but logging can easily bog down a script. Script Debugger’s Result View won’t update live, but it’s ability to set breakpoints, and pause and step through code allows easy viewing of results.

Script Editor is sorely lacking any almost all of Script Debugger’s features and even if you are using a version of Mac OS X and the Developer Tools that allowed for Applescript Studio in Xcode, you’ll find that things don’t improve in the debugging feature list. It is safe to say that creating the scripts that I do would be unreasonably difficult with Script Editor.

Subroutines

With long or complex scripts, subroutines are your best tool to help manage the code. Applescript does have tools to have executable code in separate files, but management of multiple files becomes difficult when you have to install the files on another person’s machine because all paths are relative, and the Applescript framework doesn’t hunt for names files when run. You can easily add 50 lines of code just to make sure everything gets hooked up properly and in almost all cases I have found it is just easier to have the entire script in a single file.

Subroutines, obviously, help compartmentalize code and allow for easy code reuse, but you can also use them with impunity. One of my longer scripts has almost 70 subroutines, and as I add more features, I can add more subroutines and not see any change in performance, unlike adding deeply nested loops and global variables (more on that later).

When I start a project, and I know there are certain tasks that need to be done, I use a large part of the collection posted to this website to drop into the script that compartmentalizes common operations. Writing to a file, getting a file name from a path, navigating a folder structure to find files to be acted upon, and the like. Subroutines afford the opportunity to clean up the interfacing with the framework that you are working with (like writing a file to the Finder somewhere).

Subroutines manage deeply nested loops

One behavior I have noticed (bad coding practices aside), and this may only pertain to my work with InDesign but I’ve seen it often enough that it bears mentioning, is that if I nest too many loops—like six or seven deep—Applescript will start returning nil objects for items that I know to exist. Compartmentalizing those loops into subroutines always fixes that bad behavior.

Keywords my and me

As noted elsewhere on this website, subroutines in Applescript are handled a bit oddly. If you call a subroutine more than once and sending tells to external applications, then you must use my or memy is used for subroutines that don’t return a value, and me is used for subroutines that do. Since I rarely can predict how or when a particular subroutine will be used over the long term, I have simply gotten in the habit of usingme and my in all of my subroutine declarations. Doing so doesn’t cause any performance degradation near as I can tell, and I avoid unnecessary errors.

repeat with n from 1 to 3
		set end of pBork to MashValuesTogether(gAck, kGreeble) of me
end repeat

set end of pBork to MashValuesTogether(gAck, kGreeble) of me
-- this will work without "of me", but we use it anyway in case we reuse
-- this code in a script that sends a tell to an application

Property and Global Variables

Properties and Globals each have their own distinct syntax, but they are always global and mutable. Always. It is a given in most programming circles that global variables are A Bad Thing, but I feel with Applescript they are unavoidable.

The Art of Prefixing

Since long complex scripts generally have a lot of subroutines to better compartmentalize and resuse code, Applescript offers a way to share global objects. A typical global object for my scripts is an error log to compile production errors regardless of where they were incurred in my code, but the script must move on despite their appearance because there are multiple files to process.

I generally use the following convention to help me keep track of the sometimes myriad of properties and globals to manage my code because since everything is mutable, their proper use is all about intent:

  • k for properties that are intended to be immutable (the typical convention for naming constants)
  • p for properties that are intended to be mutable
  • g for global variables which are always handled as being mutable

All properties must be populated somehow at the time of their declaration, so if I have a property that is a list, I tend to declare that as a property to get it out of the way. Globals cannot be populated at the time of their declaration.

(* Immutable Properties *)
property kFoo : "Bar"
property kGreeble : "Ponies"

(* Mutable Properties *)
property pBork : {}

(* Global Properties (Mutable) *)
global gAck

Value Management

Another behavior that I sometimes come across with properties and global variable is that their values can be maintained across runtimes. Meaninf that I open a script, run it, and run it again, and find data from the first runtime mixed in the second. This doesn’t happen all the time, but it does happen (typically when I am working with a lot of data at once), and can be a source of frustration especially in the case of lists: items just keep getting appended to the same list.

To workaround this, I always create a ResetGlobalVariables() subroutine. Here I reset values for mutable properties and globals, but I leave the immutable properties alone. This isn’t a hard and fast rule because sometimes I will populate a mutable property at the beginning with a needed default value and leave it alone until it is time to change it.

on ResetGlobalVariables()
	-- Here we leave kFoo and kGreeble alone because they are already
	-- populated and are marked as immutable.
	set pBork to {}
	set gAck to kFoo
end ResetGlobalVariables

Fake Pragmas

Xcode and other editors offer pragmas a way to comment code to help define structure, but Applescript does not. A table of contents view in the tool bar at the top of the editor can become unweildly after a while, so a fake pragma can be created by naming a subroutine with all caps and possibly even prefixing it with something to make it stand out more in the list.

on __COMMON_FUNCTIONS()
	(* PRAGMA *)
end __COMMON_FUNCTIONS

Code

The following example shows all of the principles described above. This is the core structure for all of my very complex scripts.

(* Immutable Properties *)
property kFoo : "Bar"
property kGreeble : "Ponies"

(* Mutable Properties *)
property pBork : {}

(* Global Properties (Mutable) *)
global gAck

on run {}
	my ResetGlobalVariables()

	repeat with n from 1 to 3
		set end of pBork to MashValuesTogether(gAck, kGreeble) of me
	end repeat

	set end of pBork to MashValuesTogether(gAck, kGreeble) of me
	-- this will work without "of me", but we use it anyway in case we reuse
	-- this code in a script that sends a tell to an application

	return pBork
	--> RESULT: {"BarPonies", "BarPonies", "BarPonies", "BarPonies"}
end run

on __COMMON_FUNCTIONS()
	(* PRAGMA *)
end __COMMON_FUNCTIONS

on ResetGlobalVariables()
	-- Here we leave kFoo and kGreeble alone because they are already
	-- populated and are marked as immutable.
	set pBork to {}
	set gAck to kFoo
end ResetGlobalVariables

on MashValuesTogether(ThisValue, ThatValue) -- (string, string) as string
	return (ThisValue & ThatValue) as string
end MashValuesTogether

Other than that…

While other code editing software and languages have robust tools and characteristics to manage complex implementations, Applescript does not despite the fact that it can create very complex implementations itself. We must work with what we have, and since it isn’t much, we have to go back to the basics of good coding practices, such as those described in Code Complete by Steve McConnell.

Ultimately, it comes back to properly naming variables, subroutines, functions, creating self-documenting code. All of the ideas described above are for naught in the face of bad coding practices.