Making code usable, useful and testable
Writing MATLAB code is seductively easy. Polishing the functionality of algorithmic MATLAB code to make it usable, useful and testable requires a bit of design.
Taking the spellcheck code that I wrote about earlier as a strawman example, it is possible to make that functionality usable, useful and testable by wrapping it as an easy-to-use MATLAB object.
Building an API allows more users to use the spellcheck functionality in their code, tools and workflows. In this post, we shall make spellcheck available in MATLAB with an emphasis on making it easy to use.
In order to design a usable, useful and testable API, a few high-level design objectives can be articulated as follows:
Installation and access to the dependencies should be easy and automated
The spellcheck functionality is enabled by a third party library and while I could write a lot of documentation about what needs to be installed and where it goes, it becomes a lot easier to just use MATLAB to automate this.
The class definition for the spellcheck object looks like:
classdef spellcheck properties Dictionary; CommentsOnly = true; end properties(Hidden) Handle; end % Constructor and methods go here end
The properties of this class permits the specification of dictionary files and provides storage for the handle to the Jazzy library. Static methods on this class will return a string specifying where the class is located and this allows download of the library to a known relative location.
MATLAB offers a full set of commands to operate on the web and internet. Leveraging these commands, we have:
methods(Static) % SPELLROOT Method that will locate the root folder for tooling function [sRoot] = spellroot() % Folder path relative to the location of this code sRoot = fileparts(mfilename('fullpath')); end % DOWNLOADJAZZY Download and provision the Jazzy library % Fetches the Java Spell Check library from SourceForge function downloadJazzy() % Store the jar file in a known location jazzyJar = fullfile(spellcheck.spellroot,'lib','java','jazzy0-2-1.jar'); urlwrite('http://downloads.sourceforge.net/project/jazzy/OldFiles/jazzy0-2-1.jar',jazzyJar); % Add the JAR file to the MATLAB path. if exist(jazzyJar,'file') javaaddpath(jazzyJar); end end end
These few lines of code now automates the installation of the library directly from SourceForge to the user's MATLAB session.
The real implication is that it took just a few lines of code to empower our MATLAB application with the ability to leverage any of the thousands of projects on SourceForge that may be relevant for the task at hand. The web connectivity and features of the MATLAB platform are not restricted to SourceForge but extends to Github, ProjectLocker, File Exchange, etc.
In a nutshell, everything that the internet has to offer are just a few lines of MATLAB code away. MATLAB can just as easily connect to services (REST, SOAP, etc.) but that is a whole other topic.
The dictionaries that power the tool can also be fetched from SourceForge. Given that the tool is multi-language capable, we will specify the list of available dictionaries in a neutral format (CSV) for simplicity.
en_US.zip, http://downloads.sourceforge.net/project/jazzydicts/jazzydicts/Dictionaries%201.0/en_US.zip en_NZ.zip, http://downloads.sourceforge.net/project/jazzydicts/jazzydicts/Dictionaries%201.0/en_NZ.zip en_GB.zip, http://downloads.sourceforge.net/project/jazzydicts/jazzydicts/Dictionaries%201.0/en_GB.zip
The code for downloading these artifacts is nearly identical to the code for the library and has been omitted from this post for brevity.
Given that all our software artifacts are now in place, we can glue them all together. This brings us to our next design objective.
The utility should be usable to our diverse user community
MATLAB has evolved over 30 years and has a very diverse user community. From the engineer at an automotive OEM to a researcher in a biotech startup and from a student working on his thesis to a team of software professionals developing MATLAB code on Wall Street, the platform and the language has an amazing diversity in its user base.
It is interesting to note that the MATLAB language enables a well designed object to look, feel and work like a typical MATLAB function. In simpler terms, MATLAB classes can do nearly everything your procedural code can do... and some more.
To illustrate this, the design of an entry point to our class is the next step in wrapping our functionality.
methods % Constructor function obj = spellcheck(varargin) % Make Jazzy available to MATLAB import com.mathworks.spellcheck.*; % Setup a default language. obj.Dictionary = which('en_USx.dic'); % default % Create a jazzy spellchecker obj.Handle = SpellCheck(); obj.Handle.setDictionary(obj.Dictionary); obj.Handle.verbose = true; % Check and call the spellchecker if nargin==1 % Filename specified or string specified filename = which(varargin{1}); % Fire up the spellchecker if isempty(filename) % We are dealing with a string obj.check(varargin{1}); else % We are dealing with a file so read it into a string str = fileread(filename); obj.check(str); end else % Check the contents of the editor obj.checkEditor(); end end % The check() and checkEditor() methods go here end
The constructor serves as a simple switchyard allowing the user to check strings, files or the contents of the editor depending on the inputs and outputs provided to it. In this case, our constructor with no arguments will check the contents of the editor. This is not always desirable but I have left it this way for illustration purposes.
Since, the invocation of the class is identical to the invocation of an identically named MATLAB function, the code provides a familiar interface to users who wish to use this class in their procedural code. Developers who are comfortable with Object Oriented techniques can use the methods of this object for finer grained control over the utility.
% CHECK Method to check an input string function check(obj, inputStr, varargin) % If line number is specified use it for the link if ~isempty(varargin) obj.Handle.linenumber = num2str(varargin{1}); obj.Handle.checkSpelling(inputStr); else obj.Handle.checkSpelling(inputStr); end end
The check() method is where the rubber meets the road. The MATLAB code invokes the Java code and the spelling check is performed. The checkEditor method (omitted for brevity) is similar in that it reads in the current file that is open in the editor and then performs the check.
If a linenumber is specified such as in the case of the checkEditor() code, the output includes a hyperlink to the line using the MATLAB opentoline() function.
Testing our class:
>> spellcheck % Checks the content of the editor Input (172): github No suggestions Input (174): API Suggestion: AP Suggestion: APB Suggestion: APO
The class now provides me a single command to check the contents of my editor making it trivially accessible as I author content. For example, the dictionary did not contain the word github on line 172 or API on line 174 of this post.
Testing the other routes through the constructor:
>> spellcheck('majic') % Checks string >> spellcheck majic % Equivalent syntax
The spellcheck object can also be instantiated and persisted for finer grain control.
>> s = spellcheck; >> s.check('majic'); % Equivalent syntax Input : majic Suggestion: magic Suggestion: manic
Is this really multi-language capable? I test the code again setting the dictionary to German.
>> s.Dictionary = 'c:\Work\Spellchecker\public\de_DE\de_DEx.dic'; >> s.check('seprechen sie deutch') % Intentional spelling mistake Input : seprechen Suggestion: sprechen Input : deutch Suggestion: deutsch
MATLAB returns the corrected version - "sprechen sie deutsch" which sounds correct (perhaps a native speaker of the language can confirm).
Happy with the way my MATLAB functionality is working, I can finally address the last design requirement.
The utility should be testable
In its simplest form, a unit test that exercises the code looks like:
classdef testspellcheck < matlab.unittest.TestCase % TESTSPELLCHECK Simple unit test for our spellcheck methods (Test) function testSpellCheck(testCase) s = spellcheck('majic'); testCase.verifyEqual(char(s.Handle.suggestions.elementAt(0)),'magic'); end end end
I will not go into the details of building a robust regression test suite but rather dwell on the ramifications of the requirement.
The construction and use of an automated test suite is a very important part of integrating with 3rd party libraries and products. The test suite isolates our work from upstream dependency changes. If the Jazzy library changes, evolves or is improved upstream, it would naturally affect our functionality.
Having a fully automated test suite buys the ability to re-test functionality against upstream changes to the Jazzy library. In this particular case, the code is pretty stable but the reason I write about this is oftentimes, the module upstream is very actively maintained and changes very frequently. In such cases, the investment in building a regression test suite very quickly pays off.
In conclusion, as a platform MATLAB is the quintessential melting pot. A variety of technologies and techniques are available right from the language which can be leveraged easily to extend of the platform. As a developer this is useful to know as MATLAB can play well with the best-in-class technologies to create robust solutions with minimal effort.
- Category:
- API Design,
- Third Party Integration
Comments
To leave a comment, please click here to sign in to your MathWorks Account or create a new one.