Developer Zone

Advanced Software Development with MATLAB

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.




Published with MATLAB® R2015b

|
  • print

评论

要发表评论,请点击 此处 登录到您的 MathWorks 帐户或创建一个新帐户。