It is currently Thu Mar 28, 2024 4:56 am


All times are UTC - 8 hours [ DST ]




Post new topic Reply to topic  [ 11 posts ]  Go to page 1, 2  Next
Author Message
 Post subject: Rick Refactors %DTC, Step by Step
PostPosted: Tue Nov 02, 2010 10:07 pm 
Site Admin
User avatar

Joined: Mon Nov 01, 2010 1:58 pm
Posts: 205
Location: Seattle, Washington
Real Name: Frederick D. S. "Rick" Marshall
Began Programming in MUMPS: 15 Jun 1984
On Sunday, 6 June 2010, mumpster John Leo Zimmer proposed on the Hardhats mailing list that we create new versions of routine %DTC to illustrate the difference between early MUMPS and modern MUMPS, and that we use the new code as an example on the MUMPS Wikipedia article.

In the replies that follow, I'm going to take John up on his offer by refactoring the routine step by step. My main goal here is to help teach refactoring to journeyman mumpsters who are often faced with similar situations, in which they have taken responsibility for a routine written by someone else and must now master it.

For those interested in the background behind this project, I'm appending the complete thread from Hardhats

=====


ORIGINAL MESSAGE

Date: Fri, 4 Jun 2010 00:36:41 -0700 (PDT)
Subject: [Hardhats] Anyone care to comment on this?
From: rtweed <rob.tweed@gmail.com>

http://ankhos.wordpress.com/2010/06/03/ ... -the-mumps

I've already posted a comment.

Could somebody remove that damned code example from Wikipedia? It
causes more damage to people's perception of the language than
anything else I know.



RESPONSE # 1

Date: Fri, 04 Jun 2010 05:09:46 -0400
From: Joseph Dal Molin <dalmolin@e-cology.ca>
Subject: Re: [Hardhats] Anyone care to comment on this?

How about someone suggesting something to replace the Wikipedia snippet
that shows both the power of M and an example of a well written snippet.



RESPONSE # 2

Date: Fri, 4 Jun 2010 13:43:25 -0400
Subject: Re: [Hardhats] Anyone care to comment on this?
From: Sam Habiel <sam.habiel@gmail.com>

You know, the only thing that the Mumps example is missing is
comments. Otherwise it's a very nice and succinct piece of code
written in the style of early Mumps which I cannot fault given
hardware limitations and language development at that time. I don't
know if anybody realizes this, but this is the fileman routine %DTC,
an integral part of VISTA (see documentation at
http://www.hardhats.org/fileman/pm/cl_dtc1.htm).

The author's comparison isn't fair. This code does some pretty damned
complex stuff (gives you the difference between 2 dates); the python
code is just a potboiler.

I personally cannot argue with the author's point: most of VISTA code
looks like that; and is difficult for outsiders to refactor. And the
history of VISTA development informs us that it pretty much froze any
development in the early to mid-90's; much of the innovations of the
90 and 95 M standards have not been applied (parameter passing; newing
variables; never mind transactions). Notice in this example that the
programmers gets the parameters from the symbol table an kills the
variables at the end of the snippet... that pretty much dates it to
before 1990.

Sam



RESPONSE # 3

Date: Fri, 4 Jun 2010 14:22:02 -0700 (PDT)
Subject: [Hardhats] Re: Anyone care to comment on this?
From: rtweed <rob.tweed@gmail.com>

Sam

Sorry but I strongly and passionately disagree.

At the time it was written it was.....perhaps....the way you would
write this kind of stuff, but I'm sorry, to perpetuate and/or sanction
that style of indecipherable code is inexcusable these days. As I
say, it's precisely this kind of code that gives Mumps the universal
raspberry that it consistently gets...and I have to say I'm not
surprised. That %DTC routine could easily be rewritten to be much
clearer in terms of what it's doing.

As I've noted, if I employed someone who wrote code that like these
days....well they wouldn't be employed too long. Succinct it may be,
but it's as clear as mud, even to me who has been using Mumps since
1980. Even in those days I'd have had words with any developer in our
team that wrote something like that. Nice it certainly ain't.

It has to be removed from the Wikipedia page if we want anyone to take
Mumps seriously as a language fit for purpose in the 21st century

Rob



RESPONSE # 4

Date: Fri, 4 Jun 2010 15:05:26 -0700 (PDT)
Subject: [Hardhats] Re: Anyone care to comment on this?
From: "kdtop3@gmail.com" <kdtop3@gmail.com>

Rob,

Have you tried to change the wikipedia page and been shot down?

Maybe you could add something to show the way we can use mumps in
2010.

Kevin



RESPONSE # 5

Subject: RE: [Hardhats] Re: Anyone care to comment on this?
Date: Fri, 4 Jun 2010 22:19:53 -0500
From: "Bhaskar, KS" <KS.Bhaskar@FISglobal.com>

Didn't Jim Self have a converter that would automatically convert between
the abbreviated and full forms of MUMPS?

Regards
-- Bhaskar



RESPONSE # 6

Date: Fri, 4 Jun 2010 22:56:34 -0400
Subject: Re: [Hardhats] Re: Anyone care to comment on this?
From: JohnLeo Zimmer <johnleozim@gmail.com>

It would be an interesting exercise to rewrite %DTC in a manner free
of the constraints imposed on XAK by the systems available in those
ancient times.

It would also make a more interesting Wikipedia article.
jl.z



RESPONSE # 7

Date: Sat, 5 Jun 2010 00:45:12 -0700 (PDT)
Subject: [Hardhats] Re: Anyone care to comment on this?
From: rtweed <rob.tweed@gmail.com>

Bhaskar

That won't help - expanding the abbreviation won't make the logic any
clearer - that's the problem: you simply wouldn't write logic like
this any more. I'm sure it's all very clever stuff and very
efficient, but even if you expand it out it will still be
incomprehensible. By that I mean that you can't look at the code and
immediately understand 2 things:

- what is it doing?
- why is it doing it?

Unless you can quickly understand these 2 things, the code is
basically unmaintainable, even if you threw in loads of comments

Simple example - take the line:

Code:
TOH  S %H=%M>2&'(%Y#4)+$P("^31^59^90^120^151^181^212^243^273^304^334","^",%M)+%D


Now what the hell is this all about? It's actually quite simple but
you wouldn't guess it from those hieroglyphics. So let's figure it
out:

That nasty $p bit ought to be separated out into something more
comprehensible:

Code:
getCumulativeDays(month)
     new days,counter
     s counter="^31^59^90^120^151^181^212^243^273^304^334"
     s days=$p(cumulativeDays,"^",month)
     QUIT days


Now that detail is out of the way and inherently explained by the
function name, we can begin to tidy up that line and make it clearer
what's going on:

Code:
TOH  S %H=%M>2&'(%Y#4)+$$getCumulativeDays(%M)+%D


Still clear as mud, so let's re-write the whole thing:

Code:
convertToHValue(days,month)
     new hValue,totalDays
     set totalDays=$$getCumulativeDays(month)
     set hValue=totalDays+days
     if month>2,year#4=0 set hValue=hValue+1
    QUIT hValue


Now it's clearer what it's doing it, and it's hopefully a lot clearer
why it's doing it too, so now we have something that is beginning to
look like it might be maintainable. A lot more wordy, yes, but on
today's hardware nobody will even notice any difference in execution
time, but the really costly thing - programmer time to do maintenance
- will be a lot more efficient.

Unfortunately, this kind of transformation isn't something any code
converter is going to manage to achieve.

Rob



RESPONSE # 8

Date: Sat, 5 Jun 2010 11:28:55 -0700 (PDT)
Subject: [Hardhats] Re: Anyone care to comment on this?
From: George Timson <gtimson@post.harvard.edu>

OK, now, this guy orlowski admits he is being "a tad unfair".

He illustrates with MUMPS the following task:
"For any two dates in the past 200 years, find the number of days
between them, remembering that 1900 was not a leap year."
He illustrates with Python the following task:
"Write out the four names 'john', 'pat', 'gary', and 'michael'"

May I opine that these two tasks differ in complexity by at least a
couple of orders of magnitude, and that in fact the second task is not
one that anyone would write a real program for?

VA got what they paid for. In thirty years they never (except for
Y2K) paid for a re-factoring of FileMan, and so they did not get it.
They still run with code designed, like this, to process the minimal
number of stack levels, and to fit in the tiniest of partitions.

I might mention by the way that this code _is_ commented, in a
separate corpus of documentation that the VA holds.

And also by the way, even though XAK's initials are on this routine
(the late lamented Michael Distaso, that is), his memory should not be
sullied with the blame for this mess. Fact is, I wrote it. And I
wrote it _before_ 1980.

Yours,

--George Timson



RESPONSE # 9

Date: Sat, 5 Jun 2010 11:35:41 -0700 (PDT)
Subject: [Hardhats] Re: Anyone care to comment on this?
From: George Timson <gtimson@post.harvard.edu>

Actually, I understated the unfairness here. There are _two_ MUMPS
tasks performed in this snippet:

> "For any two dates in the past 200 years, find the number of days
> between them, remembering that 1900 was not a leap year."
> "For any date in the past 200 years, and a given positive or negative integer, add or subtract that interger number of days and return the resulting date"


--George Timson



RESPONSE # 10

Date: Sat, 5 Jun 2010 11:56:20 -0400
Subject: Re: [Hardhats] Re: Anyone care to comment on this?
From: Sam Habiel <sam.habiel@gmail.com>

Wow! I can now understand it.

Sam



RESPONSE # 11

Date: Sun, 6 Jun 2010 00:53:25 -0700 (PDT)
Subject: [Hardhats] Re: Anyone care to comment on this?
From: rtweed <rob.tweed@gmail.com>

George

A tad unfair, perhaps, but Mr Orlowski's reaction to his perecption of
what Mumps code must be like, based on the example he saw in
Wikipedia, was hardly surprising. And therein is the issue I'm
wanting to focus on, rather than an analysis of the code example
itself.

The fact is that the inclusion of that particular block of old,
historic code as an example of what Mumps code looks like does the
entire worldwide community of Mumps developers a massive disservice.
Every practitioner of every other language looking at that page will
believe that's the kind of indecipherable code that all of us Mumps
programmers must write, and, not unreasonably they run a mile and/or
fall about laughing.

If you really think about Mr Orlowski's trivial example and why he
juxtaposed it against the Mumps example, what he was really
demonstrating was that Python code easily meets those two critical
criteria I mentioned in my previous post:

- what does the code do?
- why does it do it?

His example, albeit trivial, meets those criteria, and his tacit
implication is that the same will be true if you write complex
applications using Python.

By comparison, the Mumps code meets neither of these criteria. Indeed
for all he knows that Mumps code might be doing something as trivial
as his example. As the author, *you* know that the code is actually
doing some complex date manipulation logic, but the code doesn't shout
that out loud and clear to Mr Orlowski or any other reader. His
assumption is, not unreasonably, that if this is an example of what
Mumps code must be like, then *all* Mumps applications must be
unmaintainable gibberish.

Now of course, *we* all know that modern Mumps code can be just as
expressive and comprehensible as any other mainstream language. The
purpose of my previous posting was two-fold:

- to demonstrate that Mumps code can be written in a way that meets
those two criteria. Indeed well-written modern Mumps looks
surprisingly similar to corresponding Python code

- to point out to Bhaskar that simply automatically expanding the
example code is not going to help - any non-Mumps reader will still
see indecipherable code that doesn't meet the two key criteria.
Manual refactoring is the only option if that example is to be used.

The bottom line is that the example in the Wikipedia page is one of
the key reasons why Mumps continues to get such a bad press out there
and is continually lambasted by practitioners of other languages. It
has to go. It does none of us any favours.

Rob



RESPONSE # 12

Date: Sun, 6 Jun 2010 07:27:17 -0400
Subject: [Hardhats] Was: Anyone care to comment on this? Refactoring exercise
for SCM
From: JohnLeo Zimmer <johnleozim@gmail.com>

I propose a small side-project for next week's VCM:

Let's run an exercise to evolve new versions of %DTC that are faithful
to the original's function but with no other restraint other than
clarity. SAC need not apply, and we are not necessarily seeking to
replace the legacy code which obviously does function. :-)

By the end of the week dialog among conference participants may lead
to a consensus on a version that can be posted to Wikipedia alongside
the legacy version.



RESPONSE # 13

Date: Sun, 6 Jun 2010 05:05:16 -0700 (PDT)
Subject: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: rtweed <rob.tweed@gmail.com>

Sounds like a great idea!

Not sure about alongside the legacy version btw - I'd completely
replace it for all the reasons I've outlined before.

Rob



RESPONSE # 14

Date: Sun, 6 Jun 2010 08:30:44 -0400
Subject: Re: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: JohnLeo Zimmer <johnleozim@gmail.com>

IMHO the legacy code is important for understanding the history of
MUMPS. And keeping the legacy %DTC would preempt the M debunkers who
can pull 20,000 other examples of dense code from FOIA. I would
envision an article that shows:

Modern code:

Legacy code (circa 1980, PDP hardware, etc.):

Reference to the history:

jl.z



RESPONSE # 15

Date: Sun, 6 Jun 2010 05:57:50 -0700 (PDT)
Subject: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: rtweed <rob.tweed@gmail.com>

Provided it's suitably explained, then I guess you may be right.

You know, I can't help feeling there's also another dimension to this,
with interesting timing given the recent VistA Modernization report
and the attendee list for the VCM..

Seems to me that the very same issue affects the VA itself -
management look inside VistA and they see all this legacy Mumps code.
Their advisors, consultants etc all look at it too and roll their eyes
in horror, as do eager detractors and competitors from the sidelines.
Are any one of them aware that the code doesn't need to be like this?
Have any of them seen what modern Mumps code could look like as a
replacement for the legacy code?

Rather than sink the kinds of funds they are talking about into
rewriting in some other Open Source language, why not do the rewrite
using modern Mumps techniques that would produce code that was just as
readable as anything like Python etc can produce....but with the
advantage that it would automatically run alongside the old legacy
code and migration from old to new would be seamless. The
modernization report notes the need to fully understand the existing
logic, so an exercise of poring over all the legacy code and making
sense of it is recognised as a requirement anyway.

Even more reason, then, for your proposed exercise, perhaps? A real,
working illustration of legacy Mumps versus modern Mumps.

One last though: time, also, perhaps, to ditch the old protocols that
merely perpetuate a style of coding within VistA that belongs to a
bygone age?



RESPONSE # 16

Date: Sun, 6 Jun 2010 12:15:03 -0400
Subject: Re: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: JohnLeo Zimmer <johnleozim@gmail.com>

On Sun, Jun 6, 2010 at 8:57 AM, rtweed <rob.tweed@gmail.com> wrote:
...snip
One last though: time, also, perhaps, to ditch the old protocols that
merely perpetuate a style of coding within VistA that belongs to a
bygone age?
..snip

For this exercise, I would require only clarity as our standard.

The VA SAC could support another discussion for another day.

GrampaZ



RESPONSE # 17

Date: Sun, 6 Jun 2010 09:36:45 -0700 (PDT)
Subject: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: "kdtop3@gmail.com" <kdtop3@gmail.com>

Zimmer,

As much as you all have heard me rail against old coding styles, I
think that going back and trying to re-write working code is a waste
of time.

I consider those code blocks to be same as a compiled .dll on a
windows machine. Sure I can break out by debugger and decompiler and
pick through what they do. But what it the point? As long as the
input and output are consistent and reliable, then I just use the code
as a black box. The same applies for %DTC.

We all know that %DTC *could* be written to back it easier to ready by
humans, but unless we are wanting the change the functionality, then
when we have put the hours of redoing the code, what have we achieved?

Instead, I would argue to put the effort into training a new
generation of mumps programmers to write code that is easier to
understand and extend. And I suspect that we are preaching to the
choir, as many of us know ARE coding this way. I have even notice
that George T. is coding more this way in his newer stuff.

But a fact of the matter (IMHO) is that mumps *is* a limited language,
compared to all the bells and whistles available in newer computer
languages. I consider it more of a database scripting language. If
I had my wishes, we would instantly convert all VistA into something
more modern. But there is no point to that line of thought. The
reason I learned mumps was to support VistA. Rob is pursuing new
business with mumps, and I wish him well (he will probably take
exception to my mumps assesment), but otherwise, I don't see any
purpose in trying to evangelize mumps. And if people want to hate on
M, then let them.

Kevin



RESPONSE # 18

Date: Sun, 06 Jun 2010 13:09:48 -0400
From: Joseph Dal Molin <dalmolin@e-cology.ca>
Subject: Re: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM

Given that the two code examples (M and Python) are not even related in
terms of functionality... how about simply finding a good example of
clearly written code that accomplishes what Rob Tweed is suggesting
instead of re-writing the example. This is not evangelizing IMHO.... it
is removing ammo for FUD flingers which is very important to
evangelizing VistA which we definitely should be doing and are....

J.



RESPONSE # 19

Date: Mon, 7 Jun 2010 08:55:37 -0400
Subject: Re: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: JohnLeo Zimmer <johnleozim@gmail.com>

Kevin,

As I said above, I do not propose an attempt to <actually> replace the
working legacy version of ^%DTC that currently sits in VistA. This
would be an <<exercise>> to demonstrate that MUMPS is able to support
a more modern coding style focused on clarity and maintainability.

I would suggest that, like Rob, you have yourself contributed a number
of examples that could be used to show such a coding style is
possible. I think using ^%DTC as the starting point just helps define
the exercise... and would make it logical to add the winning design to
Wikipedia alongside the legacy version.

regards,
Zimmer



RESPONSE # 20

Date: Mon, 07 Jun 2010 10:27:10 -0400
From: "K.S. Bhaskar" <ks.bhaskar@fisglobal.com>
Subject: Re: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM

There are some changes that I would like to make to the attached, but I=20
would like to offer it as a starting point for a more modern looking M=20
program.

The changes I would like to make are divide up the range into blocks of=20
size n integers and then to have each of m processes work on the next=20
range of integers.

Regards
-- Bhaskar

Code:
threeen1e
        ; Find the maximum number of steps for the 3n+1 problem for all integers through two input integers.
        ; See http://docs.google.com/View?id=dd5f3337_12fzjpqbc2
        ; Assumes input format is 3 integers separated by a space with the first integer smaller than the second.
        ; Third integer is number of threads.  No input error checking is done.
        ; This program is modified to show that the GT.M key-value store can use arbitrary strings for
        ; both keys and values - instead of numeric subscripts and values to store the numver of steps, each
        ; subscript and value is spelled out using the strings in the program source line labelled "digits".
        ;
        ; K.S. Bhaskar 20100228
        ;
        ; No claim of copyright is made with respect to this program.
        ;
        ; Variables do not have to be declared before use, but are New'd in subprograms to ensure that they
        ; do not conflict with names in the caller.
        ;
        ; The program uses strings in different languages for each digit in the database.  It reads the program
        ; source at the label digits to get strings (separated by ;) for each language used.
digits  ;zero;eins;deux;tres;quattro;пять;ستة;सात;捌;ஒன்பது
        Do digitsinit                           ; Initialize data for conversion between integers and strings
        ;
        ; Read and process each line in turn.  Since process may be restarting after a crash, any
        ; data in database is assumed to be as recovered after a crash.  After computing and writing
        ; results from this run, database is cleared for next line of input or next run of the program.
        ;
        ; Loop for ever, read a line (quit on end of file), process that line
        For  Read input Quit:$ZEOF!'$Length(input)  Do      ; input has entire input line
        . Set i=$Piece(input," ",1)                         ; i - starting number is first piece of input
        . Set j=$Piece(input," ",2)                         ; j - ending number is second piece of input
        . Set k=$Piece(input," ",3)                         ; k - parallel execution streams requested is third piece
        . ; Reproduce input on output, formatting numbers
        . Write $FNumber(i,",",0)," ",$FNumber(j,",",0)," ",$FNumber(k,",",0)
        . ;
        . ; Get number of CPUs, calculate number of parallel execution streams, calculate block size
        . Open "cpus":(COMMAND="grep -i ^processor /proc/cpuinfo|wc -l":READONLY)::"PIPE"
        . Use "cpus" Read cpus Use $PRINCIPAL    ; Number of CPUS on system is in /proc/cpuinfo
        . Close "cpus"
        . Set:4*cpus>k k=4*cpus                  ; At least four execution streams per CPU
        . Write " ",k                            ; Report actual number of execution streams
        . Set blk=(j-i+k)\k                      ; Calculate size of blocks (last block may be smaller)
        . ;
        . ; Launch jobs.  Grab lock l1, atomically increment counter, compute and launch one job for each block of numbers.
        . ; Each child job locks l2(pid), decrements the counter and tries to grab lock l1(pid).
        . ; When counter is zero, all jobs have started.  Parent releases lock l1 and tries to grab lock l2.
        . ; When all children have released their l2(pid) locks, they're done and parent can gather & report results.
        . Set ^count=0                           ; Clear count - may have residual value if restarting from crash
        . Lock +l1                               ; Set lock for process synchronization
        . For s=i:blk:j Do
        ..  Set c=$Increment(^count)              ; Atomic increment of counter in database for process synchronization
        ..  Set tmp=$Select(s+blk-1>j:j,1:s+blk-1)         ; Compute upper limit for numbers for next child Job
        ..  Set def=$ZTRNLNM("gtm_tmp") Set:'$Length(def) def=$ZTRNLNM("PWD")     ; Working directory for Jobbed process
        ..  Set err=$Text(+0)_"_"_$Job_"_"_s_".mje"                               ; STDERR for Jobbed process
        ..  Set out=$Extract(err,1,$Length(err)-1)_"o"                            ; STDOUT for Jobbed process
        ..  Set cmd="doblk(s,tmp,i,j):(ERROR="""_err_""":OUTPUT="""_out_""":DEFAULT="""_def_""")"     ; Command to Job
        ..  Job @cmd                             ; Job child process for next block of numbers
        . For  Quit:'^count  Hang 0.1            ; Wait for processes to start (^count goes to 0 when they do)
        . Lock -l1                               ; Release lock so processes can run
        . Set startat=$HOROLOG                   ; Get starting time
        . Lock +l2                               ; Wait for processes to finish
        . ;
        . ; When parent gets lock l2, child processes have completed and parent gathers and reports results.
        . set endat=$HOROLOG                     ; Get ending time - time between startat and endat is the elapsed time
        . ; Calculate duration
        . Set duration=(86400*($Piece(endat,",",1)-$Piece(startat,",",1)))+$Piece(endat,",",2)-$Piece(startat,",",2)
        . Write " ",$FNumber(^result,",",0)     ; Show largest number of steps for the range i through j
        . Write " ",$FNumber(^highest,",",0)    ; Show the highest number reached during the computation
        . Write " ",$FNumber(duration,",",0)    ; Show the elapsed time
        . Write " ",$FNumber(^updates,",",0)    ; Show number of updates
        . Write " ",$FNumber(^reads,",",0)      ; Show number of reads
        . ; If duratation is greater than 0 seconds, display update and read rates
        . Write:duration " ",$FNumber(^updates/duration,",",0)," ",$FNumber(^reads/duration,",",0)
        . Write !
        . Lock -l2                               ; Release lock for next run
        . Do dbinit                              ; Initialize database for next run
        Quit
        ;
dbinit  ; Entryref dbinit clears database between lines
        Kill ^count,^highest,^reads,^result,^step,^updates
        Quit
        ;
digitsinit                                      ; Initialize arrays to convert between strings and integers
        New m,x
        Set x=$Text(digits)
        For m=0:1:9 Set di($Piece(x,";",m+2))=m,ds(m)=$Piece(x,";",m+2)
        Quit
        ;
inttostr(n)                                     ; Convert an integer to a string
        New m,s
        Set s=ds($Extract(n,1))
        For m=2:1:$Length(n) Set s=s_" "_ds($Extract(n,m))
        Quit s
        ;
strtoint(s)                                     ; Convert a string to an integer
        New m,n
        Set n=di($Piece(s," ",1))
        For m=2:1:$Length(s," ") Set n=10*n+di($Piece(s," ",m))
        Quit n
        ;
        ; This is where Jobbed processes start
doblk(myfirst,mylast,allfirst,alllast)
        Set reads=0,updates=0,highest=alllast   ; Start with zero reads and zero writes, highest number is at least alllast
        Do digitsinit                           ; Initialize data for conversion between integers and strings
        Lock +l2($JOB)                          ; Get lock l2 that parent will wait on till this Jobbed processes is done
        If $Increment(^count,-1)                ; Decrement ^count to say this process is alive
        Lock +l1($JOB)                          ; This process will get lock l1($JOB) only parent has released lock on l1
        Do dostep(myfirst,mylast)                        ; Do the block assigned to this process
        Do:alllast>mylast dostep(mylast+1,alllast)       ; Help with the numbers after this process' block, if there are any
        Do:allfirst<myfirst dostep(allfirst,myfirst-1)   ; Help with the numbers before this process' block, if there are any
        TStart ()                               ; Update global statistics inside a transaction
        ; The following line unconditionally adds the number of reads & write performed by this process to the
        ; number of reads & writes performed by all processes, and sets the highest for all processes if the
        ; highest calculated by this process is greater than that calculated so far for all processes
        Set:$Increment(^reads,reads)&$Increment(^updates,updates)&(highest>$Get(^highest)) ^highest=highest
        TCommit
        Lock -l1($JOB),-l2($JOB)                ; Release locks to tell parent this parent is done
        Quit                                    ; Jobbed processes terminate here
        ;
dostep(first,last)                              ; Calculate the maximum number of steps from first through last
        New current,currpath,i,n
        For current=first:1:last Do
        . Set n=current                         ; Start n at current
        . Kill currpath                         ; Currpath holds path to 1 for current
        . ; Go till we reach 1 or a number with a known number of steps
        . For i=0:1 Quit:$Increment(reads)&($Data(^step($$inttostr(n)))!(1=n))  Do
        ..  Set currpath(i)=n                   ; log n as current number in sequence
        ..  Set n=$Select('(n#2):n/2,1:3*n+1)   ; compute the next number
        ..  Set:n>highest highest=n             ; see if we have a new highest number reached             
        . Do:0<i                                ; if 0=i we already have an answer for n, nothing to do here
        ..  If 1<n Set i=i+$$strtoint(^step($$inttostr(n)))
        ..  TStart ()                           ; Atomically set maximum
        ..  Set:i>$Get(^result) ^result=i
        ..  TCommit
        ..  Set n="" For  Set n=$Order(currpath(n)) Quit:""=n  Set:$Increment(updates) ^step($$inttostr(currpath(n)))=$$inttostr(i-n)
        Quit




RESPONSE # 21

Date: Mon, 7 Jun 2010 08:44:37 -0600
Subject: Re: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: drew einhorn <drew.einhorn@gmail.com>

When changing the Wikipedia page:

http://en.wikipedia.org/wiki/MUMPS

You should also look at and consider its discussion page:

http://en.wikipedia.org/wiki/Talk:MUMPS

And the archived copy of earlier discussions:

http://en.wikipedia.org/wiki/Talk:MUMPS/Archive01

Given Wikipedia's strong preference for citations of published
material from reliable sources over "original material" (which is
strongly deprecated in the Wikipedia). You should publish your
"original" examples elsewhere, perhaps in a tutorial section on the
hardhats website, and cite them in your revised wikipedia article. In
the meantime you should cite and discuss examples of contemporary,
well written M code in VistA, WorldVistA, OpenVista, and/or other open
source distributions, etc.

Drew Einhorn

"You can see a lot by just looking."
-- Yogi Berra



RESPONSE # 22

Date: Mon, 07 Jun 2010 10:48:55 -0400
From: "K.S. Bhaskar" <ks.bhaskar@fisglobal.com>
Subject: Re: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM

On the web, it's easy to publish references to quote! See an earlier
version of the program at http://docs.google.com/View?id=3Ddd5f3337_15hssw6=bdh

Regards
-- Bhaskar



RESPONSE # 23

Date: Mon, 7 Jun 2010 09:04:48 -0600
Subject: Re: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: drew einhorn <drew.einhorn@gmail.com>

On Mon, Jun 7, 2010 at 8:48 AM, K.S. Bhaskar <ks.bhaskar@fisglobal.com> wro=
te:
> On the web, it's easy to publish references to quote! See an earlier
> version of the program at http://docs.google.com/View?id=3Ddd5f3337_15hss=w6bdh
>

It would be even better to "publish" it in a place that the Wikipedia
would consider a reliable, authoritative source such as a hardhats
tutorial, a VA publication, an opensource project with significant
adoption, etc. Better yet a collection of citations from several of
the above.

This is only a bit more difficult, and will result in a revision to
the wikipedia that is more likely to survive the wikipedia edit wars.

Publishing the referenced article on a respected site would be far
more likely to get the respect of the wikipedia's editors, than it's
current status as an anonymous google doc with no signature or any
other identification of its author and the author's credentials.



RESPONSE # 24

Date: Mon, 7 Jun 2010 12:13:49 -0400
Subject: Re: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: Steven McPhelan <smcphelan@alumni.uci.edu>

Why? Hardly anyone use %DTC in their code today when they have the same
functionality in the XLFDT utilities. It seems to me that the intent of
this original message thread is available in the XLFDT utilities. I do not
know the number of years it has been since I last actually used ^%DTC.

--
Steve
"Is life so dear or peace so sweet as to be purchased at the price of chains
and slavery? Forbid it, Almighty God! I know not what course others may
take, but as for me, give me liberty, or give me death!" - Patrick Henry



RESPONSE # 25

Date: Mon, 7 Jun 2010 13:31:06 -0700 (PDT)
Subject: [Hardhats] Re: Was: Anyone care to comment on this? Refactoring
exercise for SCM
From: OldMster <msires@verizon.net>

I think several are missing the point. John is proposing an exercise
that will only be a tool to debunk the myth that 'mumps is too hard to
learn' or 'mumps is too hard to support'. What he proposes would be a
tool to show that old Mumps code was written that way for a reason -
not because the language required it, but because the environment
(hardware, O/S, etc) required it.

I also agree with Kevin that Mumps has always been a database
scripting language, and in that role it has no peer (in my less than
humble opinion). It had user IO capabilities because it had to (at
the time), and the fact that Open and Use command parameters were
never part of the standard (how to specify them was, but the actual
parameters were implementation specific) contributes to this
argument. In today's world, using other technologies for the 'user'
end, and continuing to use Mumps for the database manipulation is a
very powerful and supportable system.

As an aside, I have usually silenced those preaching 'mumps is too
hard to learn' with the comment that I wasn't sure I'd hire someone as
a programmer that found learning 26 command ands and 26 functions a
difficult task. Yes, I know there aren't exactly 26 of each, but the
number is close, and given that almost all can be abbreviated to a
single character it is an easy number to use. The 'mumps is too hard
to support' argument is more difficult, given that Mumps programmers
(me included) have perpetuated the old style long beyond the point
where it actually was useful. My personal shop rule revolves around
how fast I can figure out what is going on at 3:00am - If it takes me
too long to figure it out, you're fired! :-)

Mark



RESPONSE # 26

Date: Fri, 11 Jun 2010 08:54:18 -0700
From: "Frederick D. S. Marshall" <rick.marshall@vistaexpertise.net>
Subject: [Hardhats] MUMPS Refactoring Exercise [Was: Anyone care to comment on
this? Refactoring exercise for SCM]

Dear Kevin,

Like you I see little point in trying to evangelize MUMPS. I generally
agree that if people want to hate on MUMPS we should let them. Our job
isn't public relations; it's VISTA.

There are, though, reasons why refactoring %DTC is more than an exercise
in PR. The most important reasons to refactor dense code like this into
a more readable style are so the person doing the refactoring can (a)
improve their mastery of MUMPS, which is not as simple a language as it
seems on the surface, and (b) above all improve their mastery of the
specific VISTA package containing the dense code.

A) All programming languages are limited, and yes, this includes MUMPS,
whose limitations grate most on those of us used to other languages. But
so too is haiku limited, yet who would deny the greatness of that form
of poetry and spend their time railing against its extraordinarily
strict limitations. MUMPS is freedom itself compared to haiku, and is
capable of expressing equally profound things within its chosen domain
of medical informatics, which is why MUMPS dominates medical software.
Becoming great with MUMPS requires more than noticing those limitations.
It also requires learning to use them, learning how the joints of the
language articulate between those points of limitation, learning how to
use those limitations to create the idiom of your own fluent personal
MUMPS programming style.

Developing from MUMPS journeyman to mastery very much comes from
exercises like the one Doctor Z has proposed here, from mastering
difficult pieces of code by transforming them into our personal MUMPS
styles. A MUMPS journeyman is someone who has studied enough and
practiced enough to be capable of doing exercises like this. A MUMPS
master is someone who has already done this so many times that they've
built up a library of MUMPS code that they've created or refactored into
their own style that together accomplishes something impressive and
worthy, a masterwork. That journey from journeyman to mastery changes
each of us from someone who gags at the thought of having to edit a
routine like %DTC to someone who instead grins and rolls up his sleeves.

B) Part of any true transfer of VISTA authority from one generation to
the next is accomplished by having the new generation re-express the
complete algorithm of the package in place, in MUMPS, in their own
style, to make it all maximally readable to them personally (not just
generally). This compels them to sift through the code - all of the code
- until they understand every line, every variable, and every loop,
decision, and branch in the flow of control. That degree of
understanding of a package cannot be acquired any other way and is the
only secure foundation for the emerging VISTA renaissance, for the grand
projects to come.

When a hundred new programmers immerse themselves in VISTA's hundred
packages, we'll be well on our way back to having VISTA under effective
development, to being capable of making VISTA bloom spectacularly again.
Where the VA has gone wrong for years now is above all in trying to rush
to the exciting projects without first investing in the hard work of
remastering the code, and the results speak for themselves. Trying to
remove or replace things we don't understand is where a surprising
percentage of history's truly terrible results come from - and those of
us who have mastered our chosen areas of VISTA learn enough to
understand that they should not be removed or replaced when they can so
much more easily, cheaply, and safely be refactored and refined. Our
focus needs to be not so much on the code and how refactoring changes
it, but on the programmers involved and what refactoring the code
teaches them. That is more than a worthy investment - helping them
become tomorrow's VISTA masters needs to be our top priority as a community.

I agree with Steve McPhelan (as usual) that most of us don't use %DTC to
do our date conversions any more, because Wally Fort's XLFDT function
library does the same things using true parameter passing, functions,
and variable scoping. Nevertheless, %DTC is in active use throughout the
existing VISTA code, so it needs to be understood, and its very density
makes it a more useful exercise in refactoring than the XLFDT library.

I agree to the Doctor Z challenge, and I would encourage as many other
Mumpsters as possible out there to take it on, too. For my Paideia
students, let's make this our next joint homework assignment together,
to get things started back up again in anticipation of this summer's new
classes. Let's exercise our refactoring muscles and create a library of
%DTC variations that demonstrates many different styles in which we can
program in MUMPS.

To help protect that diversity, rather than posting the results publicly
just yet, please e-mail them to me and Doctor Z privately. Reply to this
Hardhats e-mail if you agree to the challenge and again when you're done
without posting the actual result publicly, so people can follow the
progress of the challenge. I'll collect and catalog them with Doctor Z,
and then on July 11th we'll post them all on Vistapedia for review and
reply here to relaunch the discussion.

Who else is up to the challenge?

Yours truly,
Rick



RESPONSE # 27

Date: Fri, 11 Jun 2010 09:02:26 -0700
From: "Frederick D. S. Marshall" <rick.marshall@vistaexpertise.net>
Subject: Re: [Hardhats] MUMPS Refactoring Exercise [Was: Anyone care to
comment on this? Refactoring exercise for SCM]

I'll just add that anyone wanting to tackle this project should have
handy the VA Fileman Programmer Manual to refer to the definitions of
the public supported entry points this routine contains.



RESPONSE # 28

Date: Fri, 11 Jun 2010 09:13:25 -0700
From: "Frederick D. S. Marshall" <rick.marshall@vistaexpertise.net>
Subject: Re: [Hardhats] MUMPS Refactoring Exercise [Was: Anyone care to
comment on this? Refactoring exercise for SCM]

To help anyone who wants to get started, here is DIDTC as a text file
with tabs for the line starts to improve legibility. Good luck.

Code:
DIDTC   ;SFISC/XAK-DATE/TIME OPERATIONS ;20AUG2009
   ;;22.0;VA FileMan;**14,36,71,117,1035**;Mar 30, 1999
   ;Per VHA Directive 10-93-142, this routine should not be modified.
D   N %T
   I 'X1!'X2 S X="",%Y=0 Q
   S X=X1 D H S X1=%H,X=X2,X2=%Y+1 D H S X=X1-%H,%Y=%Y+1&X2
   K %H,X1,X2 Q
   ;
C   N %,%T,%Y
   S X=X1,X2=+$G(X2) I 'X S (X,%H)="" Q
   D H S %H=%H+X2 D YMD S:$P(X1,".",2) X=X_"."_$P(X1,".",2) K X1,X2 Q
S   S %=%#60/100+(%#3600\60)/100+(%\3600)/100 Q
   ;
H   ;called from DIG, DIP4
   I X<1410000 S (%H,%T)=0,%Y=-1 Q
   S %Y=$E(X,1,3),%M=$E(X,4,5),%D=$E(X,6,7)
   S %T=$E(X_0,9,10)*60+$E(X_"000",11,12)*60+$E(X_"00000",13,14)
TOH   N DILEAP D
   . N Y S Y=%Y+1700 S:%M<3 Y=Y-1
   . S DILEAP=(Y\4)-(Y\100)+(Y\400)-446 Q
   S %H=$P("^31^59^90^120^151^181^212^243^273^304^334","^",%M)+%D
   S %=('%M!'%D),%Y=%Y-141
   S %H=(%H+(%Y*365)+DILEAP+%),%Y=$S(%:-1,1:%H+4#7)
   K %M,%D,% Q
   ;
DOW   D H S Y=%Y K %H,%Y Q
   ;
DW   D H S Y=%Y,X=$P("SUN^MON^TUES^WEDNES^THURS^FRI^SATUR","^",Y+1)_"DAY"
   S:Y<0 X="" Q
   ;
7   I '%H S (%,X)="" Q
   S %=(%H>21608)+(%H>94657)+%H-.1,%Y=%\365.25+141,%=%#365.25\1
   S %D=%+306#(%Y#4=0+365)#153#61#31+1,%M=%-%D\29+1
   S X=%Y_"00"+%M_"00"+%D Q
   ;
YX   ;called from DIV, etc
   D YMD S Y=X_% Q:Y=""  G DD^%DT
   ;
YMD    ;called from DIP5. Documented entry point for converting a date/time %H in $H format into a date (in X) and time (in %) in FileMan internal format.
   I %H[",0" S %=%H N %H S %H=%-1_",86400"
   N %D,%M,%Y D 7 S %=$P(%H,",",2) D S
   Q
   ;
   ;
T   ;from %DT
   F %=1:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("TODAY",%)'=Y
   S X=$E(X,%+1,99) G PM:Y=""
   I X?1.N1"M" S %H=$H D MONTH G D^%DT
   I +X'=X D DMW S X=%
   G:'X 1^%DT
PM   S @("%H=$H"_Y_X) D TT G 1^%DT:%I(3)'?3N,D^%DT
   ;
   ;
N   ;from %DT
   F %=2:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("NOW",%)'=Y
   I Y="" S %H=$H D %H G RT
   S X=$E(X,%+1,99)
   I X?1.N1"H" S X=X*3600,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"'" S X=X*60,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"M" S %H=$H D %H,MONTH G RT1
   D DMW G 1^%DT:'% S @("%H=$H"_Y_%),%H=%H_","_$P($H,",",2) D %H
RT   D TT
RT1   S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24) I %DT'["S" S %=+$E(%,1,12)
   Q:'$D(%(0))  S Y=% G E^%DT
   ;
   ;
PF   ;from %DT
   S %H=$H D YMD S %(9)=X,X=%DT["F"*2-1 I @("%I(1)*100+%I(2)"_$E("> <",X+2)_"$E(%(9),4,7)") S %I(3)=%I(3)+X
   Q
   ;
   ;
MONTH   ;Add months to current date
   S Y=Y_+X
   D TT
   S %=%I(1)+Y,%I(1)=%-1#12+1,%I(3)=%I(3)+(%-$S(%>0:1,1:12)\12)
   S %="31^"_($$LEAP(%I(3))+28)_"^31^30^31^30^31^31^30^31^30^31"
   I %I(2)>$P(%,U,%I(1)) S %I(2)=$P(%,U,%I(1))
   S X=%I(3)_"00"+%I(1)_"00"+%I(2)
   Q
   ;
LEAP(X)   ;Return 1 if leap year
   S:X<1700 X=X+1700
   Q '(X#4)&(X#100)!'(X#400)
   ;
TT   N %M,%D,%Y D 7 S %I(1)=%M,%I(2)=%D,%I(3)=%Y
   Q
   ;
NOW   S %H=$H,%H=$S($P(%H,",",2):%H,1:%H-1)
   D TT S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24) Q
   ;
DMW   S %=$S(X?1.N1"D":+X,X?1.N1"W":X*7,X?1.N1"M":X*30,+X=X:X,1:0)
   Q
   ;
%H   I '$P(%H,",",2) S %H=%H-1 Q
   I $P(%H,",",2)<60&(%DT'["S") S $P(%H,",",2)=60
   Q
   ;
COMMA   ;
   S %D=X<0 S:%D X=-X S %=$S($D(X2):+X2,1:2),X=$J(X,1,%),%=$L(X)-3-$E(23456789,%),%L=$S($D(X3):X3,1:12)
   F %=%:-3 Q:$E(X,%)=""  S X=$E(X,1,%)_","_$E(X,%+1,99)
   S:$D(X2) X=$E("$",X2["$")_X S X=$J($E("(",%D)_X_$E(" )",%D+1),%L) K %,%D,%L
   Q
   ;
   ;
   ;
HELP   S DDH=$S($D(DDH):DDH,1:0),A1="Examples of Valid Dates:" D %
   I %DT["M" D  G 0
   . S A1="  "_$S(%DT["I":1.1957,1:"JAN 1957 or JAN 57")_$S(%DT'["N":" or 0157",1:"") D %
   . S A1="  T    (for this month)" D %
   . S A1="  T+3M (for 3 months in the future)" D %
   . S A1="  T-3M (for 3 months ago)" D %
   . S A1="Only month and year are accepted. You must omit the precise day." D %
   S A1="  "_$S(%DT["I":"20.1.1957",1:"JAN 20 1957 or 20 JAN 57")_" or "_$S(%DT["I":"20/1",1:"1/20")_"/57"_$S(%DT'["N":" or "_$S(%DT["I":200157,1:"012057"),1:"") D %
   S A1="  T   (for TODAY),  T+1 (for TOMORROW),  T+2,  T+7,  etc." D %
   S A1="  T-1 (for YESTERDAY),  T-3W (for 3 WEEKS AGO), etc." D %
   S A1="If the year is omitted, the computer " D  D %
   . I %DT["P" S A1=A1_"assumes a date in the PAST." Q
   . I %DT["F" S A1=A1_"assumes a date in the FUTURE." Q
   . S A1=A1_"uses CURRENT YEAR.  Two digit year" D %
   . S A1="  assumes no more than 20 years in the future, or 80 years in the past."
   . Q
   I %DT'["X" S A1="You may omit the precise day, as:  "_$S(%DT["I":1,1:"JAN,")_" 1957" D %
   I %DT'["T",%DT'["R" G 0
   S A1="If only the time is entered, the current date is assumed." D %
   S A1="Follow the date with a time, such as "_$S(%DT["I":"20.1",1:"JAN 20")_"@10, T@10AM, 10:30, etc." D %
   S A1="You may enter a time, such as NOON, MIDNIGHT or NOW." D %
   S A1="You may enter   NOW+3'  (for current date and time Plus 3 minutes" D %
   S A1="  *Note--the Apostrophe following the number of minutes)" D %
   I %DT["S" S A1="Seconds may be entered as 10:30:30 or 103030AM." D %
   I %DT["R" S A1="Time is REQUIRED in this response." D %
0   Q:'$D(%DT(0))
   S A1=" " D % S A1="Enter a date which is "_$S(%DT(0)["-":"less",1:"greater")_" than or equal to " D %
   S Y=$S(%DT(0)["-":$P(%DT(0),"-",2),1:%DT(0)) D DD^%DT:Y'["NOW"
   I '$D(DDS) W Y,"." K A1 Q
   S DDH(DDH,"T")=DDH(DDH,"T")_Y_"." K A1 Q
   ;
%   I '$D(DDS) W !,"     ",A1 Q
   S DDH=DDH+1,DDH(DDH,"T")="     "_A1 Q
   Q




RESPONSE # 29

Date: Fri, 11 Jun 2010 10:58:13 -0700 (PDT)
Subject: [Hardhats] Re: MUMPS Refactoring Exercise [Was: Anyone care to
comment on this? Refactoring exercise for SCM]
From: Joel <joelivey@gmail.com>

Rick, I have accepted your challenge, used XTMREDO on it, and e-mailed
you the result.



RESPONSE # 30

Date: Thu, 17 Jun 2010 05:57:54 -0700
Subject: Re: [Hardhats] Re: MUMPS Refactoring Exercise [Was: Anyone care to
comment on this? Refactoring exercise for SCM]
From: Sam Habiel <sam.habiel@gmail.com>

What's "XTMREDO"? I don't have it in the latest worldvista (I am testing
the 3-09 version).

Sam



RESPONSE # 31

Date: Thu, 17 Jun 2010 12:19:09 -0500
Subject: Re: [Hardhats] Re: MUMPS Refactoring Exercise [Was: Anyone care to
comment on this? Refactoring exercise for SCM]
From: David Whitten <whitten@worldvista.org>

Joel is amazing as you already know. This is some of his handiwork...

Code:
XTMREDO  ;JLI/FO-OAK-ROUTINE TO MAKE ROUTINES EASIER TO READ ;09/18/09 13:16
         ;;7.3;TOOLKIT;**to be determined**;
         ;
         ;  The code in this routine is based on that in routine XINDX8,
         ;  it has simply been modified to make the Structured Format listing
         ;  into an actual routine.  Conversion should not alter the
         ;  functioning of the routine.
         ;

_________________
Frederick D. S. "Rick" Marshall, VISTA Expertise Network, 819 North 49th Street, Suite 203, Seattle, Washington 98103, (206) 465-5765, rick dot marshall at vistaexpertise dot net
"The hidden harmony is best." - Heraclitus of Ephesus


Last edited by toad on Mon Feb 13, 2017 4:56 pm, edited 1 time in total.
removing 3D insertions, replace m with month


Top
Offline Profile  
 
 Post subject: Rick Refactors %DTC, Step Zero
PostPosted: Tue Nov 02, 2010 10:21 pm 
Site Admin
User avatar

Joined: Mon Nov 01, 2010 1:58 pm
Posts: 205
Location: Seattle, Washington
Real Name: Frederick D. S. "Rick" Marshall
Began Programming in MUMPS: 15 Jun 1984
Before I begin my refactoring, it's important for me to clarify my position that early MUMPS code is too frequently criticized by people who didn't have to develop complex hospital systems under those incredible constraints. I'm refactoring this routine not because it needs it - the code is run probably over a hundred thousand times a day in live hospital environments and works beautifully. Rather, I'm doing it to help illustrate the process of refactoring as clearly as possible by picking a dramatic example, one in which the results will be quite different. The odds are good that I'll introduce bugs in the process of refactoring that will have to be discovered and removed during testing - one of the risks of refactoring - but my hope is that by taking advantage of the larger routine sizes, improved variable scoping, and block-structuring features available in more modern versions of MUMPS, the resulting code will be more maintainable by less experienced mumpsters.

If anyone else wants to refactor this routine step by step as I'm doing, but according to different priorities and styles than I'll be using, I encourage them to start a new thread within this forum to make it easier to follow the course of their refactoring.

Here is the version of %DTC I'm beginning with. As George Timson noted in the Hardhats thread, much of this predates 1980. That oldest code is therefore 1977 MUMPS (that is, from the first MUMPS standard), though the routine has been edited many times and so includes newer MUMPS as well.

=====

Code:
DIDTC   ;SFISC/XAK-DATE/TIME OPERATIONS ;20AUG2009
   ;;22.0;VA FileMan;**14,36,71,117,1035**;Mar 30, 1999
   ;Per VHA Directive 10-93-142, this routine should not be modified.
D   N %T
   I 'X1!'X2 S X="",%Y=0 Q
   S X=X1 D H S X1=%H,X=X2,X2=%Y+1 D H S X=X1-%H,%Y=%Y+1&X2
   K %H,X1,X2 Q
   ;
C   N %,%T,%Y
   S X=X1,X2=+$G(X2) I 'X S (X,%H)="" Q
   D H S %H=%H+X2 D YMD S:$P(X1,".",2) X=X_"."_$P(X1,".",2) K X1,X2 Q
S   S %=%#60/100+(%#3600\60)/100+(%\3600)/100 Q
   ;
H   ;called from DIG, DIP4
   I X<1410000 S (%H,%T)=0,%Y=-1 Q
   S %Y=$E(X,1,3),%M=$E(X,4,5),%D=$E(X,6,7)
   S %T=$E(X_0,9,10)*60+$E(X_"000",11,12)*60+$E(X_"00000",13,14)
TOH   N DILEAP D
   . N Y S Y=%Y+1700 S:%M<3 Y=Y-1
   . S DILEAP=(Y\4)-(Y\100)+(Y\400)-446 Q
   S %H=$P("^31^59^90^120^151^181^212^243^273^304^334","^",%M)+%D
   S %=('%M!'%D),%Y=%Y-141
   S %H=(%H+(%Y*365)+DILEAP+%),%Y=$S(%:-1,1:%H+4#7)
   K %M,%D,% Q
   ;
DOW   D H S Y=%Y K %H,%Y Q
   ;
DW   D H S Y=%Y,X=$P("SUN^MON^TUES^WEDNES^THURS^FRI^SATUR","^",Y+1)_"DAY"
   S:Y<0 X="" Q
   ;
7   I '%H S (%,X)="" Q
   S %=(%H>21608)+(%H>94657)+%H-.1,%Y=%\365.25+141,%=%#365.25\1
   S %D=%+306#(%Y#4=0+365)#153#61#31+1,%M=%-%D\29+1
   S X=%Y_"00"+%M_"00"+%D Q
   ;
YX   ;called from DIV, etc
   D YMD S Y=X_% Q:Y=""  G DD^%DT
   ;
YMD    ;called from DIP5. Documented entry point for converting a date/time %H in $H format into a date (in X) and time (in %) in FileMan internal format.
   I %H[",0" S %=%H N %H S %H=%-1_",86400"
   N %D,%M,%Y D 7 S %=$P(%H,",",2) D S
   Q
   ;
   ;
T   ;from %DT
   F %=1:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("TODAY",%)'=Y
   S X=$E(X,%+1,99) G PM:Y=""
   I X?1.N1"M" S %H=$H D MONTH G D^%DT
   I +X'=X D DMW S X=%
   G:'X 1^%DT
PM   S @("%H=$H"_Y_X) D TT G 1^%DT:%I(3)'?3N,D^%DT
   ;
   ;
N   ;from %DT
   F %=2:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("NOW",%)'=Y
   I Y="" S %H=$H D %H G RT
   S X=$E(X,%+1,99)
   I X?1.N1"H" S X=X*3600,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"'" S X=X*60,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"M" S %H=$H D %H,MONTH G RT1
   D DMW G 1^%DT:'% S @("%H=$H"_Y_%),%H=%H_","_$P($H,",",2) D %H
RT   D TT
RT1   S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24) I %DT'["S" S %=+$E(%,1,12)
   Q:'$D(%(0))  S Y=% G E^%DT
   ;
   ;
PF   ;from %DT
   S %H=$H D YMD S %(9)=X,X=%DT["F"*2-1 I @("%I(1)*100+%I(2)"_$E("> <",X+2)_"$E(%(9),4,7)") S %I(3)=%I(3)+X
   Q
   ;
   ;
MONTH   ;Add months to current date
   S Y=Y_+X
   D TT
   S %=%I(1)+Y,%I(1)=%-1#12+1,%I(3)=%I(3)+(%-$S(%>0:1,1:12)\12)
   S %="31^"_($$LEAP(%I(3))+28)_"^31^30^31^30^31^31^30^31^30^31"
   I %I(2)>$P(%,U,%I(1)) S %I(2)=$P(%,U,%I(1))
   S X=%I(3)_"00"+%I(1)_"00"+%I(2)
   Q
   ;
LEAP(X)   ;Return 1 if leap year
   S:X<1700 X=X+1700
   Q '(X#4)&(X#100)!'(X#400)
   ;
TT   N %M,%D,%Y D 7 S %I(1)=%M,%I(2)=%D,%I(3)=%Y
   Q
   ;
NOW   S %H=$H,%H=$S($P(%H,",",2):%H,1:%H-1)
   D TT S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24) Q
   ;
DMW   S %=$S(X?1.N1"D":+X,X?1.N1"W":X*7,X?1.N1"M":X*30,+X=X:X,1:0)
   Q
   ;
%H   I '$P(%H,",",2) S %H=%H-1 Q
   I $P(%H,",",2)<60&(%DT'["S") S $P(%H,",",2)=60
   Q
   ;
COMMA   ;
   S %D=X<0 S:%D X=-X S %=$S($D(X2):+X2,1:2),X=$J(X,1,%),%=$L(X)-3-$E(23456789,%),%L=$S($D(X3):X3,1:12)
   F %=%:-3 Q:$E(X,%)=""  S X=$E(X,1,%)_","_$E(X,%+1,99)
   S:$D(X2) X=$E("$",X2["$")_X S X=$J($E("(",%D)_X_$E(" )",%D+1),%L) K %,%D,%L
   Q
   ;
   ;
   ;
HELP   S DDH=$S($D(DDH):DDH,1:0),A1="Examples of Valid Dates:" D %
   I %DT["M" D  G 0
   . S A1="  "_$S(%DT["I":1.1957,1:"JAN 1957 or JAN 57")_$S(%DT'["N":" or 0157",1:"") D %
   . S A1="  T    (for this month)" D %
   . S A1="  T+3M (for 3 months in the future)" D %
   . S A1="  T-3M (for 3 months ago)" D %
   . S A1="Only month and year are accepted. You must omit the precise day." D %
   S A1="  "_$S(%DT["I":"20.1.1957",1:"JAN 20 1957 or 20 JAN 57")_" or "_$S(%DT["I":"20/1",1:"1/20")_"/57"_$S(%DT'["N":" or "_$S(%DT["I":200157,1:"012057"),1:"") D %
   S A1="  T   (for TODAY),  T+1 (for TOMORROW),  T+2,  T+7,  etc." D %
   S A1="  T-1 (for YESTERDAY),  T-3W (for 3 WEEKS AGO), etc." D %
   S A1="If the year is omitted, the computer " D  D %
   . I %DT["P" S A1=A1_"assumes a date in the PAST." Q
   . I %DT["F" S A1=A1_"assumes a date in the FUTURE." Q
   . S A1=A1_"uses CURRENT YEAR.  Two digit year" D %
   . S A1="  assumes no more than 20 years in the future, or 80 years in the past."
   . Q
   I %DT'["X" S A1="You may omit the precise day, as:  "_$S(%DT["I":1,1:"JAN,")_" 1957" D %
   I %DT'["T",%DT'["R" G 0
   S A1="If only the time is entered, the current date is assumed." D %
   S A1="Follow the date with a time, such as "_$S(%DT["I":"20.1",1:"JAN 20")_"@10, T@10AM, 10:30, etc." D %
   S A1="You may enter a time, such as NOON, MIDNIGHT or NOW." D %
   S A1="You may enter   NOW+3'  (for current date and time Plus 3 minutes" D %
   S A1="  *Note--the Apostrophe following the number of minutes)" D %
   I %DT["S" S A1="Seconds may be entered as 10:30:30 or 103030AM." D %
   I %DT["R" S A1="Time is REQUIRED in this response." D %
0   Q:'$D(%DT(0))
   S A1=" " D % S A1="Enter a date which is "_$S(%DT(0)["-":"less",1:"greater")_" than or equal to " D %
   S Y=$S(%DT(0)["-":$P(%DT(0),"-",2),1:%DT(0)) D DD^%DT:Y'["NOW"
   I '$D(DDS) W Y,"." K A1 Q
   S DDH(DDH,"T")=DDH(DDH,"T")_Y_"." K A1 Q
   ;
%   I '$D(DDS) W !,"     ",A1 Q
   S DDH=DDH+1,DDH(DDH,"T")="     "_A1 Q
   Q

_________________
Frederick D. S. "Rick" Marshall, VISTA Expertise Network, 819 North 49th Street, Suite 203, Seattle, Washington 98103, (206) 465-5765, rick dot marshall at vistaexpertise dot net
"The hidden harmony is best." - Heraclitus of Ephesus


Top
Offline Profile  
 
 Post subject: Rick Refactors %DTC, Step One
PostPosted: Tue Nov 02, 2010 11:36 pm 
Site Admin
User avatar

Joined: Mon Nov 01, 2010 1:58 pm
Posts: 205
Location: Seattle, Washington
Real Name: Frederick D. S. "Rick" Marshall
Began Programming in MUMPS: 15 Jun 1984
The first stage of refactoring MUMPS code requires numerous steps; its purpose is to work out the skeleton of the code - its calling structure.

MUMPS is a very dynamic language, so subroutine boundaries are not necessarily fixed nor necessarily clear even when they are. Likewise, calls to subroutines may be fixed in the code for anyone to read or may be computed on the fly. Identifying which subroutines exist within the routine, how they're structured, what they're for, who they call, and who calls them - knowing these things is a prerequisite for understanding what you can and cannot change about the routine. A private subroutine can be completely rewritten or even eliminated, but your ability to change a publicly supported entry point is much more limited - the label has to be preserved along with any inputs and outputs, and (to borrow terminology from Bertrand Meyer's insightful design of the Eiffel programming language) you have to preserve any preconditions, postconditions, and invariants. More on those later. For now our focus is just the calling structure.

In MUMPS, unless you're the author of a routine (and sometimes even if you are, unless you're careful) it can be difficult to immediately perceive its subroutine boundaries, so the very first step is to make them impossible to miss. Here are the specific changes I made:

1) Since this routine is being modified from its primary form, I use the patch list on the second line to flag the routine as containing local modifications (i.e., secondary development).

2) As per the VA's programming standards and conventions (SAC), I always update the final ;-piece of the first line with the date and time I last edited the routine. This will change with every step.

3) Also as per the SAC, since I'm making such invasive changes to the routine, I have to add my site abbreviation and initials to the front of the first line, so future programmers know I'm responsible for it (i.e., the person to contact if things go wrong). As per VISTA tradition, I preserve the existing list of programmers, since I'm adding to their work, not truly replacing it. Specifically, the algorithms are theirs; I'm just re-expressing them.

4) After the header lines I add change-history comments to identify the classic reporter's concerns of when, who, what, and why. I usually also include where (which system I did the work on), but on June 11th I was evidently off my game. I'll correct this in a later step. If this seems verbose, consider whether it would seem so at 3 a.m. on a live system where this code was rapidly producing errors that affected patient care and it were your responsibility to fix them while the hospital director stood behind you scowling. Any high-level language can express an algorithm, but none of them can explain what you meant to do, only what you actually did, and the heart of troubleshooting is determining the difference. You should always comment not for yourself but for the inexperienced and frantic troubleshooter.

5) One of the fundamental rules of organization is the Principle of Locality: Put related things together; separate unrelated things. In routines, white space is a powerful tool for doing this. I insert two empty lines between each subroutine so anyone at a glance can see where they begin and end - two blank lines so that I can use one blank line within a subroutine to separate it into sections.

6) Lines with labels need comments to explain what the subroutine that follows is for. At this point in our refactoring we have no idea, but we do know we're going to insert those comments, so I break lines where necessary to isolate the label onto a comment line.

7) There's no guarantee in MUMPS that a label introduces a subroutine. Execution that begins at one label will continue on to the next unless an intervening quit, goto, or halt command ends processing of the current block of lines. Therefore, the terminating quit, goto, or halt is at least as important as the label in determining subroutine boundaries. However, frequently the quit, goto, or halt that ends a subroutine is not easy to spot. It may come at the end of a long, complicated line and be easy to miss. Therefore, my personal style is stylistically to treat the terminating quit, goto, or halt as a special command, to isolate it to its own line, set off by a blank line from the rest of its subroutine, and to spell it out in full to distinguish it from other quits, gotos, and hangs (since hang and halt have the same abbreviation but very different function) within the block.

8) When editing a long routine, in most routine editors you will only see part of the code at a time, often only twenty to twenty-four lines. Frequently you see the termination of a subroutine but not the label that begins it. In a complex subroutine, especially when hopping around from place to place within the code while following a path of execution during troubleshooting, it can be easy to get lost. I've found from having to support other people's code that adding a comment to subroutine termination lines to identify which subroutine they terminate can help an otherwise easily confused troubleshooter remain oriented within the code, which in turn can help them relax and stay focused on the problem rather than worrying about where they are.

9) Partly for that same reason, I add a final comment line to my routines labeled END with a comment that names the routine it belongs to. Partly, though, I do that so I know the routine listing I'm looking at is complete - or that I'm really at the end. Through various transmission mishaps, from time to time it's possible to get an incomplete routine, and when that happens it's almost always the final lines that are missing. Adding the END line is just another trick to help rule out that situation and keep troubleshooters oriented as to where they are within the routine (and which routine).

10) When you're first learning about a routine, it can be easy to forget that some labels start independent subroutines whereas others fall through from above and so are actually part of a larger block. To help identify fall-through code, (1) I only separate the labels involved by one blank line, not two, to emphasize that they are just parts of a larger subroutine, and (2) on their terminator lines I name the subroutine at first to include all the labels involved, to help emphasize in my mind that they go together, to force me to think about them as a unit. If the entire routine, or most of it, is one big fall-through, I skip this step and instead emphasize any subroutines that aren't part of the main body. This step fits %DTC well, since it's mainly a library of small subroutines, most of which involve just one label.

As a side note, %DTC is also called DIDTC - two identical routines. When the routine is changed, it's shipped about under the name DIDTC and then copied to create %DTC to make the changes take effect. %DTC is the routine that gets called throughout VISTA to help with date/time processing. In this thread I'll use the names interchangeably.

It should already be becoming clear that I have a very pronounced and idiosyncratic programming style. Future steps are only going to reinforce that impression. I want to emphasize that I don't believe my style is the only one or the right one. It's just my toolkit for making my code easier to troubleshoot, which in turn lets me write considerably more complex code than I otherwise could. I hope in the future on this board to do walkthroughs of some of my own routines to dissect what makes them tick and how I use this style to manage their complexity. Every experienced mumpster will have his or her own personal style that they use to make the code manageable to them. I think it would be a bad thing if we all wrote in the same style, because we're all different. What's important about style is that it work for you and your secondary developers, not whether it works for me, and that you understand what it is and how and why it works for you so that you can continue to develop and improve your style as you gain experience.

Likewise, my approach to refactoring is hardly the only one, but I do it the way I do to solve certain kinds of problems that arise during refactoring. I'm writing about these things in this kind of detail to proselytize neither my programming style nor my specific approach to refactoring, but only to help teach the importance and nature of both refactoring and programming style.

Here's the result of step one:

Code:
%DTC   ;SFISC/GFT,XAK,VEN/TOAD - Date/Time Operations ; 6/11/2010 9:15am
   ;;22.0;VA FileMan;**14,36,71,117,1035**LOCAL**;Mar 30, 1999
   ;Per VHA Directive 10-93-142, this routine should not be modified.
   ;
   ; 2010 06 11 VEN/TOAD: Rick Marshall began refactoring the routine as
   ; a Paideia exercise after accepting John Leo Zimmer's challenge on
   ; Hardhats. I refactored it in the following steps:
   ;
   ; 1  clearly separate the subroutines & identify their end points
   ;
   ;
D   ;
   N %T
   I 'X1!'X2 S X="",%Y=0 Q
   S X=X1 D H S X1=%H,X=X2,X2=%Y+1 D H S X=X1-%H,%Y=%Y+1&X2
   K %H,X1,X2
   ;
   QUIT  ; end of DIDTC/%DTC-D
   ;
   ;
C   ;
   N %,%T,%Y
   S X=X1,X2=+$G(X2) I 'X S (X,%H)="" Q
   D H S %H=%H+X2 D YMD S:$P(X1,".",2) X=X_"."_$P(X1,".",2) K X1,X2
   ;
   QUIT  ; end of C
   ;
   ;
S   ;
   S %=%#60/100+(%#3600\60)/100+(%\3600)/100
   ;
   QUIT  ; end of S
   ;
   ;
H   ; called from DIG, DIP4
   I X<1410000 S (%H,%T)=0,%Y=-1 Q
   S %Y=$E(X,1,3),%M=$E(X,4,5),%D=$E(X,6,7)
   S %T=$E(X_0,9,10)*60+$E(X_"000",11,12)*60+$E(X_"00000",13,14)
   ;
TOH   ;
   N DILEAP D
   . N Y S Y=%Y+1700 S:%M<3 Y=Y-1
   . S DILEAP=(Y\4)-(Y\100)+(Y\400)-446 Q
   S %H=$P("^31^59^90^120^151^181^212^243^273^304^334","^",%M)+%D
   S %=('%M!'%D),%Y=%Y-141
   S %H=(%H+(%Y*365)+DILEAP+%),%Y=$S(%:-1,1:%H+4#7)
   K %M,%D,%
   ;
   QUIT  ; end of H-TOH
   ;
   ;
DOW   ;
   D H S Y=%Y K %H,%Y
   ;
   QUIT  ; end of DOW
   ;
   ;
DW   ;
   D H S Y=%Y,X=$P("SUN^MON^TUES^WEDNES^THURS^FRI^SATUR","^",Y+1)_"DAY"
   S:Y<0 X=""
   ;
   QUIT  ; end of DW
   ;
   ;
7   ;
   I '%H S (%,X)="" Q
   S %=(%H>21608)+(%H>94657)+%H-.1,%Y=%\365.25+141,%=%#365.25\1
   S %D=%+306#(%Y#4=0+365)#153#61#31+1,%M=%-%D\29+1
   S X=%Y_"00"+%M_"00"+%D
   ;
   QUIT  ; end of 7
   ;
   ;
YX   ; called from DIV, etc
   D YMD S Y=X_% Q:Y=""
   ;
   GOTO DD^%DT ; end of YX
   ;
   ;
YMD    ; called from DIP5. Documented entry point for converting a date/time %H in $H format into a date (in X) and time (in %) in FileMan internal format.
   I %H[",0" S %=%H N %H S %H=%-1_",86400"
   N %D,%M,%Y D 7 S %=$P(%H,",",2) D S
   ;
   QUIT  ; end of YMD
   ;
   ;
T   ; from %DT
   F %=1:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("TODAY",%)'=Y
   S X=$E(X,%+1,99) G PM:Y=""
   I X?1.N1"M" S %H=$H D MONTH G D^%DT
   I +X'=X D DMW S X=%
   G:'X 1^%DT
   ;
PM   ;
   S @("%H=$H"_Y_X) D TT G 1^%DT:%I(3)'?3N
   ;
   GOTO D^%DT ; end of T-PM
   ;
   ;
N   ; from %DT
   F %=2:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("NOW",%)'=Y
   I Y="" S %H=$H D %H G RT
   S X=$E(X,%+1,99)
   I X?1.N1"H" S X=X*3600,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"'" S X=X*60,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"M" S %H=$H D %H,MONTH G RT1
   D DMW G 1^%DT:'% S @("%H=$H"_Y_%),%H=%H_","_$P($H,",",2) D %H
   ;
RT   ;
   D TT
   ;
RT1   ;
   S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24) I %DT'["S" S %=+$E(%,1,12)
   Q:'$D(%(0))  S Y=%
   ;
   GOTO E^%DT ; end of N-RT-RT1
   ;
   ;
PF   ; from %DT
   S %H=$H D YMD S %(9)=X,X=%DT["F"*2-1 I @("%I(1)*100+%I(2)"_$E("> <",X+2)_"$E(%(9),4,7)") S %I(3)=%I(3)+X
   ;
   QUIT  ; end of PF
   ;
   ;
MONTH   ; Add months to current date
   S Y=Y_+X
   D TT
   S %=%I(1)+Y,%I(1)=%-1#12+1,%I(3)=%I(3)+(%-$S(%>0:1,1:12)\12)
   S %="31^"_($$LEAP(%I(3))+28)_"^31^30^31^30^31^31^30^31^30^31"
   I %I(2)>$P(%,U,%I(1)) S %I(2)=$P(%,U,%I(1))
   S X=%I(3)_"00"+%I(1)_"00"+%I(2)
   ;
   QUIT  ; end of MONTH
   ;
   ;
LEAP(X)   ; Return 1 if leap year
   S:X<1700 X=X+1700
   ;
   QUIT '(X#4)&(X#100)!'(X#400) ; end of LEAP, return answer
   ;
   ;
TT   ;
   N %M,%D,%Y D 7 S %I(1)=%M,%I(2)=%D,%I(3)=%Y
   ;
   QUIT  ; end of TT
   ;
   ;
NOW   ;
   S %H=$H,%H=$S($P(%H,",",2):%H,1:%H-1)
   D TT S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24)
   ;
   QUIT  ; end of NOW
   ;
   ;
DMW   ;
   S %=$S(X?1.N1"D":+X,X?1.N1"W":X*7,X?1.N1"M":X*30,+X=X:X,1:0)
   ;
   QUIT  ; end of DMW
   ;
   ;
%H   ;
   I '$P(%H,",",2) S %H=%H-1 Q
   I $P(%H,",",2)<60&(%DT'["S") S $P(%H,",",2)=60
   ;
   QUIT  ; end of %H
   ;
   ;
COMMA   ;
   S %D=X<0 S:%D X=-X S %=$S($D(X2):+X2,1:2),X=$J(X,1,%),%=$L(X)-3-$E(23456789,%),%L=$S($D(X3):X3,1:12)
   F %=%:-3 Q:$E(X,%)=""  S X=$E(X,1,%)_","_$E(X,%+1,99)
   S:$D(X2) X=$E("$",X2["$")_X S X=$J($E("(",%D)_X_$E(" )",%D+1),%L) K %,%D,%L
   ;
   QUIT  ; end of COMMA
   ;
   ;
HELP   ;
   S DDH=$S($D(DDH):DDH,1:0),A1="Examples of Valid Dates:" D %
   I %DT["M" D  G 0
   . S A1="  "_$S(%DT["I":1.1957,1:"JAN 1957 or JAN 57")_$S(%DT'["N":" or 0157",1:"") D %
   . S A1="  T    (for this month)" D %
   . S A1="  T+3M (for 3 months in the future)" D %
   . S A1="  T-3M (for 3 months ago)" D %
   . S A1="Only month and year are accepted. You must omit the precise day." D %
   S A1="  "_$S(%DT["I":"20.1.1957",1:"JAN 20 1957 or 20 JAN 57")_" or "_$S(%DT["I":"20/1",1:"1/20")_"/57"_$S(%DT'["N":" or "_$S(%DT["I":200157,1:"012057"),1:"") D %
   S A1="  T   (for TODAY),  T+1 (for TOMORROW),  T+2,  T+7,  etc." D %
   S A1="  T-1 (for YESTERDAY),  T-3W (for 3 WEEKS AGO), etc." D %
   S A1="If the year is omitted, the computer " D  D %
   . I %DT["P" S A1=A1_"assumes a date in the PAST." Q
   . I %DT["F" S A1=A1_"assumes a date in the FUTURE." Q
   . S A1=A1_"uses CURRENT YEAR.  Two digit year" D %
   . S A1="  assumes no more than 20 years in the future, or 80 years in the past."
   . Q
   I %DT'["X" S A1="You may omit the precise day, as:  "_$S(%DT["I":1,1:"JAN,")_" 1957" D %
   I %DT'["T",%DT'["R" G 0
   S A1="If only the time is entered, the current date is assumed." D %
   S A1="Follow the date with a time, such as "_$S(%DT["I":"20.1",1:"JAN 20")_"@10, T@10AM, 10:30, etc." D %
   S A1="You may enter a time, such as NOON, MIDNIGHT or NOW." D %
   S A1="You may enter   NOW+3'  (for current date and time Plus 3 minutes" D %
   S A1="  *Note--the Apostrophe following the number of minutes)" D %
   I %DT["S" S A1="Seconds may be entered as 10:30:30 or 103030AM." D %
   I %DT["R" S A1="Time is REQUIRED in this response." D %
   ;
0   ;
   Q:'$D(%DT(0))
   S A1=" " D % S A1="Enter a date which is "_$S(%DT(0)["-":"less",1:"greater")_" than or equal to " D %
   S Y=$S(%DT(0)["-":$P(%DT(0),"-",2),1:%DT(0)) D DD^%DT:Y'["NOW"
   I '$D(DDS) W Y,"." K A1 Q
   S DDH(DDH,"T")=DDH(DDH,"T")_Y_"." K A1
   ;
   QUIT  ; end of HELP-0
   ;
   ;
%   ;
   I '$D(DDS) W !,"     ",A1 Q
   S DDH=DDH+1,DDH(DDH,"T")="     "_A1
   ;
   QUIT  ; end of %
   ;
   ;
END   ; end of routine DIDTC/%DTC

_________________
Frederick D. S. "Rick" Marshall, VISTA Expertise Network, 819 North 49th Street, Suite 203, Seattle, Washington 98103, (206) 465-5765, rick dot marshall at vistaexpertise dot net
"The hidden harmony is best." - Heraclitus of Ephesus


Top
Offline Profile  
 
 Post subject: Re: Rick Refactors %DTC, Step Two
PostPosted: Sun Nov 07, 2010 2:26 pm 
Site Admin
User avatar

Joined: Mon Nov 01, 2010 1:58 pm
Posts: 205
Location: Seattle, Washington
Real Name: Frederick D. S. "Rick" Marshall
Began Programming in MUMPS: 15 Jun 1984
All the steps that make up stage one of refactoring help you comprehend and annotate the routine's scope of execution, that is, where and how the flow of control shifts during execution. Until you clearly understand a routine's flow of control, you really have little idea what it does, not nearly enough to troubleshoot or enhance it effectively.

Step one clarified the subroutine boundaries.

Step two begins the process of clarifying the relationships between the subroutines. Eventually, we need to know what each subroutine is for and how and when it is called, but for now we need to annotate them with the questions we need to answer about the purpose of each subroutine and its external relationships. Again, borrowing concepts from Bertrand Meyer and his Eiffel language, we can model the relationships between subroutines as contracts that clarify what the boundaries between subroutines really mean. Understanding this will later make it possible for us to extend and even overhaul the routine comparatively easily, whereas not understanding it makes such work fraught with peril.

In much the same way that reorganizing a messy room often begins by having to catalog what you have, a process that often spreads your things out over an even wider area before you are able to put them away more compactly than before, in the same way my approach to stage one involves annotating the routine as we go, then eventually collapsing the annotations down by removing the questions and working notes and leaving just the contracts and contractual annotations.

Here are the questions we're inserting for each subroutine:

1) short description? We need seventy characters or less that summarize what the subroutine is for. Where the label line already included a comment, we save that off on its own line until we can validate it and decide where to record it if it's true.

2) public? We need to identify which subroutines are bound by external contracts, i.e., which are public? Of those that aren't, we need to identify which are private but stable, and which are not stable because they are essentially code scraps or debugging code. So we will end up with three possible answers: public, private, and debugging.

3) function? Ever since MUMPS 1990, we distinguish between subroutines that are functions and those that are procedures; for example, functions quit with an argument and are called using the extrinsic syntax $$, whereas procedures must quit without an argument and be called with a DO, JOB, or GOTO. In addition, for troubleshooting purposes we can draw on computer-science theory and distinguish true functions - those that simply calculate and return an answer - from pseudo-functions, those that use the syntax of functions but that do additional computation, such as user I/O, database updates, or so on. True functions are extremely useful in troubleshooting because if you are chasing a problem involving non-calculation computing - say a bad database update - you can ignore the functions at first when you're searching for the problem. Pseudo-functions, popularized by programming languages like C, need to be clearly flagged so troubleshooters understand they need to treat them as procedures, not functions, for debugging purposes. So here too we have three answers: function, procedure, or pseudo-function.

4) clean? MUMPS 1990 introduced strong variable scoping to MUMPS. Specifically, in MUMPS 1977 and MUMPS 1984, local variables strictly followed the chalkboard model, in which all routines running within a process could see and change all variables. In those older forms of MUMPS, coordination of local variables between different software modules was done by avoidance - each module worked in its own area of the chalkboard (by sticking with variables sharing a module-specific prefix, like PS for pharmacy variables, a practice we call namespacing) and avoided all the others. MUMPS 1990 gave us the ability to deliberately scope our local variables, so that we could cause their current state to be saved off, so we could use them in our subroutines and then have the quit automatically restore them to their former state. This meant we could write subroutines that had what we call a "small footprint" - they didn't step on any variables; after they ran, the symbol table was exactly the same as it was before they ran. A subroutine like this we call clean. One that does change variables but publicly documents all the ones it changes we call documented, since callers can anticipate the precise effect it will have on the symbol table based on the documentation. One that doesn't document what all it changes but that restricts itself to changing its namespaced variables we call namespaced. Any other subroutine is unclean, meaning its effect on the symbol table is either unknown or unpredictable. We won't get into answering this question until stage two, when we tackle variable scoping, but we pose it now along with the rest.

5) silent? Separation of input/output from other forms of computation is one of the crucial accomplishments you need in your code in order to migrate to new user interfaces (UIs), such as changing a routine from terminal-based dialog to web-based browsing. At present the possible answers to this are silent, not silent, or conditionally silent (if it calls passed-in code that may or may not be silent, for example). Eventually, though, this question will grow more sophisticated as we grapple with refactoring toward new UIs.

6) SAC-compliant? Programming enterprises have house standards for how to write MUMPS code. These standards and conventions (SAC) are often designed to support the maintainability of the large-scale architecture each routine contributes to. The Department of Veterans Affairs (VA) has its VA SAC, and Indian Health Service (IHS) has its own as well. Sometimes local developers follow the national SAC and sometimes for the sake of expediency they don't. Non-SAC code that may work perfectly fine locally might not work at all at other sites because of its failure to comply with the SAC, which is why we must have this question answered. Eventually, this question should broaden into which SAC are being adhered to, if any.

7) recursive? Recursive code is notoriously more difficult to troubleshoot for those unused to it, and in MUMPS it's possible to write code that is recursive in ways that are difficult to detect (for example, if the recursion occurs within indirection or using goto commands). Distinguish the recursive subroutines from the nonrecursive ones is just good manners. Since most subroutines are not recursive, I only answer this question if the subroutine is indeed recursive; otherwise I cut it from the header comments, as we will do for most or all of the subroutines in %DTC once we know the answers.

8) falls through from? As alluded to in step one, distinguishing the labels that are intended as entry points from those that are not is a typically Mumpsy problem, one the developer can resolve quickly with annotation but rarely chooses to, leaving the problem for anyone brave and patient enough to tackle it during refactoring.

9) called by? Any private label that isn't called at all can be eliminated. Any private label called only from a small number of other subroutines can be eliminated if those subroutines can be refactored to remove the calls. For public subroutines it's usually best to delete this question, since they can be called by any subroutine now or in the future.

10) calls? When you're trying to master a routine, one of the hardest things is remembering which subroutines call which others. I find that until I have the relationships fully and permanently memorized, I need to document them in both directions, not only by whom a subroutine is called but also who in turn that subroutine calls. Since the troubleshooter will never have all these relationships memorized, you need to document this for them.

11) calls through to? The converse of question number eight, and needed for the same reason we need question number ten.

12) input? Like question four, this starts documenting the variable scope by identifying everything that comes into the subroutine.

13) throughput? Some subroutines are calculation-oriented; that is, you give them the right inputs and they will calculate the right outputs. Others are data-oriented; that is, you give them a data structure of some kind and they update or otherwise modify it in some way. These data structures are strictly speaking neither inputs nor outputs, but both. Since they tend to be more important to the subroutine than its inputs or outputs, we highlight them with this question.

14) output? Everything that goes out of the subroutine (parameters passed by reference, function-return values, changes to the local symbol table, locks or unlocks, database updates, or device output).

15) contents? In Literate Programming, Donald Knuth made the insightful argument that as software grows in size, it exceeds the organizational capacity of our programming tools and approaches scales where the tools and techniques of book design can help. For example, except for very simple routines, it can be difficult for a troubleshooter to remember all the subroutines it contains, let alone what they do or how they're related. An easy way to help address this problem is to include a table of contents near the top of long or otherwise complex routines. Later in the refactoring process, we'll add descriptions to this list of labels, group those that belong to the same subroutine, and distinguish between public entry points and other subroutines, but for now as a placeholder and as a place to help keep track of our refactoring progress we just introduce the list of labels.

Here is the resulting routine after step two:

Code:
%DTC   ;SFISC/GFT,XAK,VEN/TOAD - Date/Time Library ; 6/11/2010 9:15am
   ;;22.0;VA FileMan;**14,36,71,117,1035**LOCAL**;Mar 30, 1999
   ;Per VHA Directive 10-93-142, this routine should not be modified.
   ;
   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   ; contents
   ;
   ; %DTC
   ; D
   ; C
   ; S
   ; H
   ; TOH
   ; DOW
   ; DW
   ; 7
   ; YX
   ; YMD
   ; T
   ; PM
   ; N
   ; RT
   ; RT1
   ; PF
   ; MONTH
   ; LEAP
   ; TT
   ; NOW
   ; DMW
   ; %H
   ; COMMA
   ; HELP
   ; 0
   ; %
   ;
   ; **LOCAL MOD** change history
   ;
   ; 2010 06 11 VEN/TOAD: Rick Marshall began refactoring the routine as
   ; a Paideia exercise after accepting John Leo Zimmer's challenge on
   ; Hardhats. I refactored it in the following steps:
   ;
   ; 1  clearly separate the subroutines & identify their end points
   ;
   ; 2  insert subroutine header comments, save original comments; create
   ;    contents list
   ;
   ;
D   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   N %T
   I 'X1!'X2 S X="",%Y=0 Q
   S X=X1 D H S X1=%H,X=X2,X2=%Y+1 D H S X=X1-%H,%Y=%Y+1&X2
   K %H,X1,X2
   ;
   QUIT  ; end of DIDTC/%DTC-D
   ;
   ;
C   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   N %,%T,%Y
   S X=X1,X2=+$G(X2) I 'X S (X,%H)="" Q
   D H S %H=%H+X2 D YMD S:$P(X1,".",2) X=X_"."_$P(X1,".",2) K X1,X2
   ;
   QUIT  ; end of C
   ;
   ;
S   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %=%#60/100+(%#3600\60)/100+(%\3600)/100
   ;
   QUIT  ; end of S
   ;
   ;
H   ; subroutine short description?
   ; original comment = called from DIG, DIP4
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I X<1410000 S (%H,%T)=0,%Y=-1 Q
   S %Y=$E(X,1,3),%M=$E(X,4,5),%D=$E(X,6,7)
   S %T=$E(X_0,9,10)*60+$E(X_"000",11,12)*60+$E(X_"00000",13,14)
   ;
TOH   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   N DILEAP D
   . N Y S Y=%Y+1700 S:%M<3 Y=Y-1
   . S DILEAP=(Y\4)-(Y\100)+(Y\400)-446 Q
   S %H=$P("^31^59^90^120^151^181^212^243^273^304^334","^",%M)+%D
   S %=('%M!'%D),%Y=%Y-141
   S %H=(%H+(%Y*365)+DILEAP+%),%Y=$S(%:-1,1:%H+4#7)
   K %M,%D,%
   ;
   QUIT  ; end of H-TOH
   ;
   ;
DOW   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   D H S Y=%Y K %H,%Y
   ;
   QUIT  ; end of DOW
   ;
   ;
DW   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   D H S Y=%Y,X=$P("SUN^MON^TUES^WEDNES^THURS^FRI^SATUR","^",Y+1)_"DAY"
   S:Y<0 X=""
   ;
   QUIT  ; end of DW
   ;
   ;
7   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I '%H S (%,X)="" Q
   S %=(%H>21608)+(%H>94657)+%H-.1,%Y=%\365.25+141,%=%#365.25\1
   S %D=%+306#(%Y#4=0+365)#153#61#31+1,%M=%-%D\29+1
   S X=%Y_"00"+%M_"00"+%D
   ;
   QUIT  ; end of 7
   ;
   ;
YX   ; subroutine short description?
   ; original comment = called from DIV, etc
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   D YMD S Y=X_% Q:Y=""
   ;
   GOTO DD^%DT ; end of YX
   ;
   ;
YMD    ; subroutine short description?
   ; original comment =  called from DIP5. Documented entry point for converting a date/time %H in $H format into a date (in X) and time (in %) in FileMan internal format.
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I %H[",0" S %=%H N %H S %H=%-1_",86400"
   N %D,%M,%Y D 7 S %=$P(%H,",",2) D S
   ;
   QUIT  ; end of YMD
   ;
   ;
T   ; subroutine short description?
   ; original comment = from %DT
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   F %=1:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("TODAY",%)'=Y
   S X=$E(X,%+1,99) G PM:Y=""
   I X?1.N1"M" S %H=$H D MONTH G D^%DT
   I +X'=X D DMW S X=%
   G:'X 1^%DT
   ;
PM   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S @("%H=$H"_Y_X) D TT G 1^%DT:%I(3)'?3N
   ;
   GOTO D^%DT ; end of T-PM
   ;
   ;
N   ; subroutine short description?
   ; original comment = from %DT
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   F %=2:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("NOW",%)'=Y
   I Y="" S %H=$H D %H G RT
   S X=$E(X,%+1,99)
   I X?1.N1"H" S X=X*3600,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"'" S X=X*60,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"M" S %H=$H D %H,MONTH G RT1
   D DMW G 1^%DT:'% S @("%H=$H"_Y_%),%H=%H_","_$P($H,",",2) D %H
   ;
RT   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   D TT
   ;
RT1   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24) I %DT'["S" S %=+$E(%,1,12)
   Q:'$D(%(0))  S Y=%
   ;
   GOTO E^%DT ; end of N-RT-RT1
   ;
   ;
PF   ; subroutine short description?
   ; original comment = from %DT
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %H=$H D YMD S %(9)=X,X=%DT["F"*2-1 I @("%I(1)*100+%I(2)"_$E("> <",X+2)_"$E(%(9),4,7)") S %I(3)=%I(3)+X
   ;
   QUIT  ; end of PF
   ;
   ;
MONTH   ; subroutine short description?
   ; original comment = Add months to current date
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S Y=Y_+X
   D TT
   S %=%I(1)+Y,%I(1)=%-1#12+1,%I(3)=%I(3)+(%-$S(%>0:1,1:12)\12)
   S %="31^"_($$LEAP(%I(3))+28)_"^31^30^31^30^31^31^30^31^30^31"
   I %I(2)>$P(%,U,%I(1)) S %I(2)=$P(%,U,%I(1))
   S X=%I(3)_"00"+%I(1)_"00"+%I(2)
   ;
   QUIT  ; end of MONTH
   ;
   ;
LEAP(X)   ; subroutine short description?
   ; original comment =  Return 1 if leap year
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S:X<1700 X=X+1700
   ;
   QUIT '(X#4)&(X#100)!'(X#400) ; end of LEAP, return answer
   ;
   ;
TT   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   N %M,%D,%Y D 7 S %I(1)=%M,%I(2)=%D,%I(3)=%Y
   ;
   QUIT  ; end of TT
   ;
   ;
NOW   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %H=$H,%H=$S($P(%H,",",2):%H,1:%H-1)
   D TT S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24)
   ;
   QUIT  ; end of NOW
   ;
   ;
DMW   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %=$S(X?1.N1"D":+X,X?1.N1"W":X*7,X?1.N1"M":X*30,+X=X:X,1:0)
   ;
   QUIT  ; end of DMW
   ;
   ;
%H   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I '$P(%H,",",2) S %H=%H-1 Q
   I $P(%H,",",2)<60&(%DT'["S") S $P(%H,",",2)=60
   ;
   QUIT  ; end of %H
   ;
   ;
COMMA   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %D=X<0 S:%D X=-X S %=$S($D(X2):+X2,1:2),X=$J(X,1,%),%=$L(X)-3-$E(23456789,%),%L=$S($D(X3):X3,1:12)
   F %=%:-3 Q:$E(X,%)=""  S X=$E(X,1,%)_","_$E(X,%+1,99)
   S:$D(X2) X=$E("$",X2["$")_X S X=$J($E("(",%D)_X_$E(" )",%D+1),%L) K %,%D,%L
   ;
   QUIT  ; end of COMMA
   ;
   ;
HELP   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S DDH=$S($D(DDH):DDH,1:0),A1="Examples of Valid Dates:" D %
   I %DT["M" D  G 0
   . S A1="  "_$S(%DT["I":1.1957,1:"JAN 1957 or JAN 57")_$S(%DT'["N":" or 0157",1:"") D %
   . S A1="  T    (for this month)" D %
   . S A1="  T+3M (for 3 months in the future)" D %
   . S A1="  T-3M (for 3 months ago)" D %
   . S A1="Only month and year are accepted. You must omit the precise day." D %
   S A1="  "_$S(%DT["I":"20.1.1957",1:"JAN 20 1957 or 20 JAN 57")_" or "_$S(%DT["I":"20/1",1:"1/20")_"/57"_$S(%DT'["N":" or "_$S(%DT["I":200157,1:"012057"),1:"") D %
   S A1="  T   (for TODAY),  T+1 (for TOMORROW),  T+2,  T+7,  etc." D %
   S A1="  T-1 (for YESTERDAY),  T-3W (for 3 WEEKS AGO), etc." D %
   S A1="If the year is omitted, the computer " D  D %
   . I %DT["P" S A1=A1_"assumes a date in the PAST." Q
   . I %DT["F" S A1=A1_"assumes a date in the FUTURE." Q
   . S A1=A1_"uses CURRENT YEAR.  Two digit year" D %
   . S A1="  assumes no more than 20 years in the future, or 80 years in the past."
   . Q
   I %DT'["X" S A1="You may omit the precise day, as:  "_$S(%DT["I":1,1:"JAN,")_" 1957" D %
   I %DT'["T",%DT'["R" G 0
   S A1="If only the time is entered, the current date is assumed." D %
   S A1="Follow the date with a time, such as "_$S(%DT["I":"20.1",1:"JAN 20")_"@10, T@10AM, 10:30, etc." D %
   S A1="You may enter a time, such as NOON, MIDNIGHT or NOW." D %
   S A1="You may enter   NOW+3'  (for current date and time Plus 3 minutes" D %
   S A1="  *Note--the Apostrophe following the number of minutes)" D %
   I %DT["S" S A1="Seconds may be entered as 10:30:30 or 103030AM." D %
   I %DT["R" S A1="Time is REQUIRED in this response." D %
   ;
0   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from HELP^%DTC
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   Q:'$D(%DT(0))
   S A1=" " D % S A1="Enter a date which is "_$S(%DT(0)["-":"less",1:"greater")_" than or equal to " D %
   S Y=$S(%DT(0)["-":$P(%DT(0),"-",2),1:%DT(0)) D DD^%DT:Y'["NOW"
   I '$D(DDS) W Y,"." K A1 Q
   S DDH(DDH,"T")=DDH(DDH,"T")_Y_"." K A1
   ;
   QUIT  ; end of HELP-0
   ;
   ;
%   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from:
   ; called by:
   ; calls:
   ; falls through to:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I '$D(DDS) W !,"     ",A1 Q
   S DDH=DDH+1,DDH(DDH,"T")="     "_A1
   ;
   QUIT  ; end of %
   ;
   ;
END   ; end of routine DIDTC/%DTC

_________________
Frederick D. S. "Rick" Marshall, VISTA Expertise Network, 819 North 49th Street, Suite 203, Seattle, Washington 98103, (206) 465-5765, rick dot marshall at vistaexpertise dot net
"The hidden harmony is best." - Heraclitus of Ephesus


Top
Offline Profile  
 
 Post subject: Rick Refactors %DTC, Step Three
PostPosted: Sun Nov 14, 2010 12:45 pm 
Site Admin
User avatar

Joined: Mon Nov 01, 2010 1:58 pm
Posts: 205
Location: Seattle, Washington
Real Name: Frederick D. S. "Rick" Marshall
Began Programming in MUMPS: 15 Jun 1984
Step three is basically about three things: (a) capturing whatever is known about the routine from documentation, (b) clarifying a bit more about the execution scope, and (c) reorganizing this rather complex routine to make its components easier to find.

1) Since this is a Fileman routine, we start by using the File Manager Programmer Manual to see if any of the labels are public entry points, and indeed quite a few are. (1.1) We make a list of these at the top of the routine (including a short description of each). (1.2) We change the end comment on each to refer to them as the full entry point name rather than just a label reference, to help remind us later when we read the code that this is a public entry point. (1.3) We answer the public? question in each one's header comments. (1.4) We move all the public subroutines ahead of the private ones. (1.5) We put them in the same order in which they appear in the documentation, to make it easier to go back and forth between the code and the routine.

2) The remaining labels and entry points may or may not be private. We don't know until we investigate the file of Database Integration Agreements, to see whether Fileman has any agreements to let specific packages use entry points that would otherwise by private. Also, being the skeptical kind, we will also later run a search of all of VISTA & RPMS (routines and database) looking for calls into ^%DTC to ferret out any undocumented dependencies. For now, we leave the question open on the undocumented labels.

3) In three cases (^%DTC, H^%DTC, and HELP^%DTC) the public entry point also includes a possibly private label (D, TOH, and 0); we won't know for sure until we search the routine and global directories for references. We adjust the comments to show the relationship between them but to leave open the possibility that they may be callable in their own right.

4) As we add header comments to the routine, one entry point (^%DTC), the entry label is increasingly separated from its code (down in D). Therefore, we separate our concerns. The header comments that describe the entry point will be kept up top near the top of the routine, ahead of the routine-overview comments. The header comments at D will be reserved for explaining D's own status as a possibly private label.

5) When we review the routine and answer the fall through from and fall through to questions, we find that aside from those three public cases, there are two cases where possibly private entry points fall through to other possibly private labels (N-RT-RT1 and T-PM). We begin referring to them as groups in our comments to help remind ourselves of their relationships.

6) For all other cases, we can now remove the two questions, having established that they don't apply. Likewise, for all public entry points we can removed the called by question, because by definition public routines can now or in the future be called by anyone.

7) We also answer the function question for every entry point we can. If it's public, we use the documentation to tell us based on how it's meant to be called. If it ends in a Quit without an argument we know it's a function. If it ends in a Quit with an argument, we wouldn't normally yet know whether it's a function or a pseudo-function, not until after we analyzed the code for side-effects; however, the only potential function we have here, LEAP, is so short we can tell at a glance it has no side-effects, so we can securely answer the question. If it ends in a Goto, like N-RT-RT1 and T-PM, we shouldn't answer the question until we study the code it branches to; even though the second subroutine contains one argumentless Quit within the main block, MUMPS allows you to write subroutines that can be called as either a procedure or a function, so we're better off waiting to answer the question until we can see the whole picture for them. As it will turn out later, both of these subroutines have important information about their execution scope waiting to be discovered during later steps in the refactoring.

8) Notice that in general we're waiting to answer variable scope questions later, after we fully understand the execution scope. There are three cases worth discussing now. First, we're accepting the documented variable scope info on the public calls because it's part of their defined contract, and therefore reliable by definition - those entry points are required to have those inputs and outputs. Second, normally, when a entry point falls through to a possibly private label, we have to keep the variable questions open for the subsequent labels, because they may be called in their own right. Third, the exception to the second case is when the possibly private label contains all of the code associated with the earlier entry point, as is the case with ^%DTC-D; in this case, D's variable info can't be different from ^%DTC's, so we cut all the variable questions from D as redundant.

Parts 3 through 8 above are all about doing the minimal investigation needed to answer the public/private and procedure/function/pseudo-function questions and the other questions that are most tightly related to them. One of the dangers in refactoring is that in truth everything is related to everything else, so if you don't have a methodical plan for how to proceed it's easy to lose track of what you've done, which will cause you to leave routines in a partially refactored (i.e., broken) state and release the code that way. This is part of the reason it took me seven times to refactor Task Manager completely when I first joined the national development team in the VA back in 1986, and at times I did release broken code. Eventually, though, I learned how the various parts of refactoring are dependent on each other, which let me do it like a recipe, carefully and methodically, annotating my progress as I go, so that now I usually only make trivial errors during refactoring that are easily captured during internal testing. The main reason I'm sharing my methodology with you in this thread is to help those of you who haven't yet mastered refactoring to avoid the mistakes I made; I hope this will help you achieve mastery of the process more quickly than I did.

9) As our annotations in the routine grow, %DTC's true complexity becomes increasingly apparent. Early studies of MUMPS noted its terseness, that in MUMPS you can write in only a few lines what it takes other languages entire programs to do. The flip side of terseness is that it's way way too easy to underestimate the complexity of a MUMPS routine - even a short routine may do vastly more than you expect - and this is especially true of early MUMPS code like this routine, since it abbreviates variable names so aggressively to fit within the 1977 MUMPS partition- and routine-size limits and to speed up those early interpreters by fitting more commands within a single line.

Not counting END, which we added, but counting %DTC itself, since it's a public entry point, this routine has twenty-seven significant labels. We can fully keep track of only five to nine things at a time in our active memory, depending on how awake, alert, calm, and focused we are, so there's no way anyone but the author will be able to remember all these labels and their relationships. We need to apply mnemonic and other organizational techniques to make it easier to think about these twenty-seven significant points. That's why in part one of this step we moved all the public entry points to the top of the routine - to divide the overall problem in half - and then put them in the order they appear in the documentation to reinforce the existing pattern and make it a more useful mnemonic for our readers.

Therefore for part nine we are going to begin addressing the other half of this problem by putting the possibly private labels in some kind of reasonable order. Eventually, we'll want to organize them based on what they do and how they're related, but we don't know those things yet, so for now we'll just put them in alphabetic order (which is a good, universally intuitive organizational default). Because we're Mumpsters, we put 7 ahead of %, violating strictly ASCII order but following MUMPS collation order.

At this point in our refactoring of this routine, we haven't yet come to grips with the hardest part of stage one - refactoring to get control of execution scope (we're still working on early mechanical steps) - we haven't touched variable scope except for what's publicly documented, and we don't even know with any certainty what half of these labels are for, but I think most people would agree the routine is already dramatically easier for a novice to read and begin to understand.

For anyone but the original author - who knows his own style and is comfortable with its original terse expression - the work so far has improved its intelligibility without altering its functionality. Nevertheless, I would never post this on, say, Wikipedia's MUMPS article as an example of more readable MUMPS code because its style lacks cohesion. Other than a few later additions, the original form of %DTC is in a consistent, coherent style; it does what it set out to do, and it does so from start to finish. Where we are in the refactoring is a chimera; part one thing, part something else. When we finish the refactoring we'll have a worthy new version worth posting as a second example for that article, but not yet.

At this point, the elegance and clarity of the routine is not even skin deep, because we're creating a palimpsest upon an original work. We're not done until it's right all the way down to its bones, like the original version is.

And, most crucially, what's left out of all these discussions, is that the most important reason to do that is not to create that end product but to transform ourselves through the process of refactoring this routine from someone who hasn't mastered it into someone who has. Refactoring is as close as we come to the original authorial process with a routine we did not in fact create. That authorial process is where real expertise, real mastery, comes from.

This is half the reason why the new VISTA renaissance is taking place outside the federal government - because we are investing in a new generation of VISTA expertise and they aren't. Unless and until the VA wrenches its budget away from doomed replacement projects and invests it instead in a massive generational training program to develop the next decades' VISTA experts, they will remain at best a passive consumer of our VISTA renaissance.

All true wealth is biological. It is not the code or the data that makes VISTA mighty. It is the mastery of that code and data that makes its rapid, ongoing development and evolution possible, makes the VISTA lifecycle highly responsive to user requests, to their wants and needs. Without that generation of masters to breathe life into the lifecycle, the code is stuck, unchanging, unresponsive, dead.

This is the most important reason why we refactor - to create a new generation of expertise.

Code:
%DTC   ;SFISC/GFT,XAK,VEN/TOAD - Date/Time Library ; 11/14/2010 9:44am
   ;;22.0;VA FileMan;**14,36,71,117,1035**LOCAL**;Mar 30, 1999
   ;Per VHA Directive 10-93-142, this routine should not be modified.
   ;
   ; about the ^%DTC entry point:
   ;
   ; returns the number of days between two dates
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; calls:
   ; falls through to: D, which is possibly private
   ;
   ; input:
   ;   X1 = first date in Fileman format (not returned)
   ;   X2 = second date in Fileman format (not returned)
   ; output:
   ;   X = # days between the two dates. X2 is subtracted from X1.
   ;   %Y = 1 if both dates have both month and day values
   ;        0 if the dates were imprecise and therefore not workable
   ;
   ;
   ; PUBLIC ENTRY POINTS
   ;
   ; ^%DTC = returns the number of days between two dates [& label D]
   ; C^%DTC = takes a date and adds or subtracts a number of days
   ; COMMA^%DTC = delimit long numbers with commas
   ; H^%DTC = converts a Fileman date/time to $H format [& label TOH]
   ; DW^%DTC = like H^%DTC but also returns name of day of week
   ; HELP^%DTC = displays a date/time help prompt [& label 0]
   ; NOW^%DTC = returns the current date/time in VA Fileman and $H formats
   ; S^%DTC = converts a $H format time to a Fileman format time
   ; YMD^%DTC = converts a $H format date to a Fileman format date
   ; YX^%DTC = converts a $H date/time to printable date/time (& FM format)
   ;
   ; NOTE: don't forget to check integration agreements for additional,
   ; private references from other packages prior to removing or
   ; otherwise fundamentally changing any of the "private" entry points.
   ;
   ; possibly private entry points:
   ;
   ; 7
   ; %
   ; %H
   ; DMW
   ; DOW
   ; $$LEAP
   ; MONTH
   ; N-RT-RT1
   ; PF
   ; T-PM
   ; TT
   ;
   ; **LOCAL MOD** change history
   ;
   ; 2010 06 11 VEN/TOAD: Rick Marshall began refactoring the routine as
   ; a Paideia exercise after accepting John Leo Zimmer's challenge on
   ; Hardhats. I refactored it in the following steps:
   ;
   ; 1  clearly separate the subroutines & identify their end points
   ;
   ; 2  insert subroutine header comments, save original comments; create
   ;    contents list
   ;
   ; 3  2010 11 14: document public entry points and reorganize routine
   ;    around them: answer questions public, procedure, falls through
   ;    from, and falls through to; delete called by for public entry
   ;    points; for public entry points answer input, output, throughput,
   ;    and subroutine short description; move public entry points to top
   ;    and in the order they appear in the documentation; alphabetize
   ;    remaining possibly private entry points
   ;
   ;
D   ; body of the ^%DTC entry point
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from: ^%DTC/DIDTC, which is public
   ; called by:
   ; calls:
   ;
   N %T
   I 'X1!'X2 S X="",%Y=0 Q
   S X=X1 D H S X1=%H,X=X2,X2=%Y+1 D H S X=X1-%H,%Y=%Y+1&X2
   K %H,X1,X2
   ;
   QUIT  ; end of ^%DTC/DIDTC-D
   ;
   ;
C   ; takes a date and adds or subtracts a number of days
   ;;public;procedure;clean?;interactive?;SAC-compliant?;recursive?
   ; calls:
   ;
   ; input:
   ;   X1 = date in Fileman format (not returned)
   ;   X2 = # days to add (positive) or subtract (negative) (not returned)
   ; output:
   ;   X = resulting date in Fileman format
   ;   %H = resulting date in $Horolog format
   ; If input includes time, so will output.
   ;
   N %,%T,%Y
   S X=X1,X2=+$G(X2) I 'X S (X,%H)="" Q
   D H S %H=%H+X2 D YMD S:$P(X1,".",2) X=X_"."_$P(X1,".",2) K X1,X2
   ;
   QUIT  ; end of C^%DTC
   ;
   ;
COMMA   ; delimit long numbers with commas
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; calls:
   ;
   ; input:
   ;   X = # to format (e.g., 42.585)
   ;   X2 = # decimal digits to output; shorter decimals will be padded
   ;        with zeroes, longer ones rounded down. Append $ (e.g., "2$") to
   ;        prefix $ in output (e.g., "$42.59". Defaults to 2.
   ;   X3 = length of output. Ignored if less than output length. Defaults
   ;        to 12.
   ; output:
   ;   X =  the formatted #, delimited by commas if long, rounded to #
   ;        decimal digits specified in X2. If X2 contained $ then $ will
   ;        precede leftmost digit. If # was negative, then output will be
   ;        in parentheses. If # was positive, a trailing space will be
   ;        appended. If formatted # is shorter than X3, it will be padded
   ;        with leading spaces to fill out the length.
   ; examples:
   ;   >S X=12345.678 D COMMA^%DTC
   ;    X="  12,345.68 "
   ;   >S X=9876.54,X2="0$" D COMMA^%DTC
   ;    X="     $9,877 "
   ;   >S X=-3,X2="2$" D COMMA^%DTC
   ;    X="     ($3.00)"
   ;   >S X=12345.678,X3=10 D COMMA^%DTC
   ;    X="12,345.68 "
   ; note:
   ;
   S %D=X<0 S:%D X=-X S %=$S($D(X2):+X2,1:2),X=$J(X,1,%),%=$L(X)-3-$E(23456789,%),%L=$S($D(X3):X3,1:12)
   F %=%:-3 Q:$E(X,%)=""  S X=$E(X,1,%)_","_$E(X,%+1,99)
   S:$D(X2) X=$E("$",X2["$")_X S X=$J($E("(",%D)_X_$E(" )",%D+1),%L) K %,%D,%L
   ;
   QUIT  ; end of COMMA^%DTC
   ;
   ;
DW   ; like H^%DTC but also returns name of day of week
   ;;public;procedure;clean?;silent?;SAC-compliant?recursive?
   ; calls:
   ;
   ; input:
   ;   X = date/time in Fileman format
   ; output:
   ;   %H = date in $H format. If X is imprecise, then the first of the
   ;        month or year is returned.
   ;   %T = time in $H format (# seconds since midnight). If X has no time
   ;        then %T = zero.
   ;   %Y = day-of-week as # from 0 to 6 (0 = Sunday and 6 = Saturday). If
   ;        X is imprecise, then %Y = -1.
   ;   X = name of the day of the week ("SUNDAY", "SATURDAY", etc.). If
   ;        X is imprecise, then X = "".
   ;
   D H S Y=%Y,X=$P("SUN^MON^TUES^WEDNES^THURS^FRI^SATUR","^",Y+1)_"DAY"
   S:Y<0 X=""
   ;
   QUIT  ; end of DW^%DTC
   ;
   ;
H   ; converts a Fileman date/time to a $H format date/time
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; original comment = called from DIG, DIP4
   ; calls:
   ; falls through to: TOH, which is possibly private
   ;
   ; input:
   ;   X = date/time in Fileman format (not returned)
   ; output:
   ;   %H = date in $H format. If X is imprecise, then the first of the
   ;        month or year is returned.
   ;   %T = time in $H format (# seconds since midnight). If X has no time
   ;        then %T = zero.
   ;   %Y = day-of-week as # from 0 to 6 (0 = Sunday and 6 = Saturday). If
   ;        X is imprecise, then %Y = -1.
   ;
   I X<1410000 S (%H,%T)=0,%Y=-1 Q
   S %Y=$E(X,1,3),%M=$E(X,4,5),%D=$E(X,6,7)
   S %T=$E(X_0,9,10)*60+$E(X_"000",11,12)*60+$E(X_"00000",13,14)
   ;
TOH   ; part two of H^%DTC
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; calls:
   ; falls through from: H^%DTC, which is public
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   N DILEAP D
   . N Y S Y=%Y+1700 S:%M<3 Y=Y-1
   . S DILEAP=(Y\4)-(Y\100)+(Y\400)-446 Q
   S %H=$P("^31^59^90^120^151^181^212^243^273^304^334","^",%M)+%D
   S %=('%M!'%D),%Y=%Y-141
   S %H=(%H+(%Y*365)+DILEAP+%),%Y=$S(%:-1,1:%H+4#7)
   K %M,%D,%
   ;
   QUIT  ; end of H^%DTC-TOH
   ;
   ;
HELP   ; displays a date/time help prompt
   ;;public;procedure;clean?;interactive;SAC-compliant?;recursive?
   ; calls:
   ; falls through to: 0, which is possibly private
   ;
   ; input:
   ;   %DT = flags to control the kind of help messages displayed:
   ;         A = Ask for date input
   ;         E = Echo the answer
   ;         F = Future dates are assumed
   ;         I = for Internationalization, assume day # precedes month #
   ;             in input.
   ;         M = only Month and year input is allowed
   ;         N = pure Numeric input is not allowed
   ;         P = Past dates are assumed
   ;         R = Requires time input
   ;         S = Seconds should be returned
   ;         T = Time input is allowed but not required
   ;         X = eXact input is required
   ;   %DT(0) = a Fileman-format date (e.g., 2690720) to allow only dates
   ;         >= that date/time. Set it negative (e.g., -2831109.15) to
   ;         allow only dates <= that date/time. Set it to NOW to allow
   ;         dates from the current time forward. Set it to -NOW to allow
   ;         dates up to the current time. All of these cases will modify
   ;         the help text displayed. Defaults to no range restrictions on
   ;         date/times other than those already specified by %DT.
   ;   See the documentation of entry point ^%DT in the Fileman Programmers
   ;   Manual for a fuller explanation of how these two parameters work.
   ; output:
   ;   the specified date/time help text will be written to the current I/O
   ;   device.
   ;
   S DDH=$S($D(DDH):DDH,1:0),A1="Examples of Valid Dates:" D %
   I %DT["M" D  G 0
   . S A1="  "_$S(%DT["I":1.1957,1:"JAN 1957 or JAN 57")_$S(%DT'["N":" or 0157",1:"") D %
   . S A1="  T    (for this month)" D %
   . S A1="  T+3M (for 3 months in the future)" D %
   . S A1="  T-3M (for 3 months ago)" D %
   . S A1="Only month and year are accepted. You must omit the precise day." D %
   S A1="  "_$S(%DT["I":"20.1.1957",1:"JAN 20 1957 or 20 JAN 57")_" or "_$S(%DT["I":"20/1",1:"1/20")_"/57"_$S(%DT'["N":" or "_$S(%DT["I":200157,1:"012057"),1:"") D %
   S A1="  T   (for TODAY),  T+1 (for TOMORROW),  T+2,  T+7,  etc." D %
   S A1="  T-1 (for YESTERDAY),  T-3W (for 3 WEEKS AGO), etc." D %
   S A1="If the year is omitted, the computer " D  D %
   . I %DT["P" S A1=A1_"assumes a date in the PAST." Q
   . I %DT["F" S A1=A1_"assumes a date in the FUTURE." Q
   . S A1=A1_"uses CURRENT YEAR.  Two digit year" D %
   . S A1="  assumes no more than 20 years in the future, or 80 years in the past."
   . Q
   I %DT'["X" S A1="You may omit the precise day, as:  "_$S(%DT["I":1,1:"JAN,")_" 1957" D %
   I %DT'["T",%DT'["R" G 0
   S A1="If only the time is entered, the current date is assumed." D %
   S A1="Follow the date with a time, such as "_$S(%DT["I":"20.1",1:"JAN 20")_"@10, T@10AM, 10:30, etc." D %
   S A1="You may enter a time, such as NOON, MIDNIGHT or NOW." D %
   S A1="You may enter   NOW+3'  (for current date and time Plus 3 minutes" D %
   S A1="  *Note--the Apostrophe following the number of minutes)" D %
   I %DT["S" S A1="Seconds may be entered as 10:30:30 or 103030AM." D %
   I %DT["R" S A1="Time is REQUIRED in this response." D %
   ;
0   ; part two of HELP^%DTC
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from: HELP^%DTC, which is public
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   Q:'$D(%DT(0))
   S A1=" " D % S A1="Enter a date which is "_$S(%DT(0)["-":"less",1:"greater")_" than or equal to " D %
   S Y=$S(%DT(0)["-":$P(%DT(0),"-",2),1:%DT(0)) D DD^%DT:Y'["NOW"
   I '$D(DDS) W Y,"." K A1 Q
   S DDH(DDH,"T")=DDH(DDH,"T")_Y_"." K A1
   ;
   QUIT  ; end of HELP^%DTC-0
   ;
   ;
NOW   ; returns the current date/time in VA Fileman and $H formats
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; calls:
   ;
   ; input: none
   ; output:
   ;   % = Fileman date/time down to the second
   ;   %H = $H date/time
   ;   %I(1) = numeric value of the month
   ;   %I(2) = numeric value of the day
   ;   %I(3) = numeric value of the year
   ;   X = Fileman date only
   ;
   S %H=$H,%H=$S($P(%H,",",2):%H,1:%H-1)
   D TT S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24)
   ;
   QUIT  ; end of NOW^%DTC
   ;
   ;
S   ; converts a $H format time to a Fileman format time
   ;;public;procedure;clean;silent?;SAC-compliant;recursive?
   ; calls:
   ;
   ; input:
   ;   % = # indicating the number of seconds from midnight
   ;       (e.g., $P($H,",",2) or 44504)
   ; output:
   ;   % = decimal part of a Fileman date (e.g., .122144)
   ;
   S %=%#60/100+(%#3600\60)/100+(%\3600)/100
   ;
   QUIT  ; end of S^%DTC
   ;
   ;
YMD    ; converts a $H format date to a Fileman format date
   ; original comment =  called from DIP5. Documented entry point for converting a date/time %H in $H format into a date (in X) and time (in %) in FileMan internal format.
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; calls:
   ;
   ; input:
   ;   %H = $H format date/time (not returned)
   ; output:
   ;   X = date in Fileman format
   ;   % = time down to the second in Fileman format (as a decimal)
   ;       If %H does not include time, % = zero.
   ;
   I %H[",0" S %=%H N %H S %H=%-1_",86400"
   N %D,%M,%Y D 7 S %=$P(%H,",",2) D S
   ;
   QUIT  ; end of YMD^%DTC
   ;
   ;
YX   ; converts a $H date/time to printable date/time (& FM format)
   ; original comment = called from DIV, etc
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; calls:
   ; branches to: DD^%DT
   ;
   ; input:
   ;   %H = date and time in $H format; time is optional. (not returned)
   ; output:
   ;   Y = date in external format. Will include time in external format
   ;       including seconds if %H contained seconds.
   ;   X = date in Fileman format
   ;   % = time in Fileman format (as a decimal value). If %H did not
   ;       include time, % = zero.
   ;
   D YMD S Y=X_% Q:Y=""
   ;
   GOTO DD^%DT ; end of YX^%DTC
   ;
   ;
7   ; subroutine short description?
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I '%H S (%,X)="" Q
   S %=(%H>21608)+(%H>94657)+%H-.1,%Y=%\365.25+141,%=%#365.25\1
   S %D=%+306#(%Y#4=0+365)#153#61#31+1,%M=%-%D\29+1
   S X=%Y_"00"+%M_"00"+%D
   ;
   QUIT  ; end of 7
   ;
   ;
%   ; subroutine short description?
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I '$D(DDS) W !,"     ",A1 Q
   S DDH=DDH+1,DDH(DDH,"T")="     "_A1
   ;
   QUIT  ; end of %
   ;
   ;
%H   ; subroutine short description?
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I '$P(%H,",",2) S %H=%H-1 Q
   I $P(%H,",",2)<60&(%DT'["S") S $P(%H,",",2)=60
   ;
   QUIT  ; end of %H
   ;
   ;
DMW   ; subroutine short description?
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %=$S(X?1.N1"D":+X,X?1.N1"W":X*7,X?1.N1"M":X*30,+X=X:X,1:0)
   ;
   QUIT  ; end of DMW
   ;
   ;
DOW   ; subroutine short description?
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   D H S Y=%Y K %H,%Y
   ;
   QUIT  ; end of DOW
   ;
   ;
LEAP(X)   ; subroutine short description?
   ; original comment =  Return 1 if leap year
   ;;public?;function;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S:X<1700 X=X+1700
   ;
   QUIT '(X#4)&(X#100)!'(X#400) ; end of $$LEAP, return answer
   ;
   ;
MONTH   ; subroutine short description?
   ; original comment = Add months to current date
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S Y=Y_+X
   D TT
   S %=%I(1)+Y,%I(1)=%-1#12+1,%I(3)=%I(3)+(%-$S(%>0:1,1:12)\12)
   S %="31^"_($$LEAP(%I(3))+28)_"^31^30^31^30^31^31^30^31^30^31"
   I %I(2)>$P(%,U,%I(1)) S %I(2)=$P(%,U,%I(1))
   S X=%I(3)_"00"+%I(1)_"00"+%I(2)
   ;
   QUIT  ; end of MONTH
   ;
   ;
N   ; subroutine short description?
   ; original comment = from %DT
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ; falls through to: RT-RT1
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   F %=2:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("NOW",%)'=Y
   I Y="" S %H=$H D %H G RT
   S X=$E(X,%+1,99)
   I X?1.N1"H" S X=X*3600,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"'" S X=X*60,%H=$H,@("X=$P(%H,"","",2)"_Y_X),%=$S(X<0:-1,1:0)+(X\86400),X=X#86400,%H=$P(%H,",")+%_","_X G RT
   I X?1.N1"M" S %H=$H D %H,MONTH G RT1
   D DMW G 1^%DT:'% S @("%H=$H"_Y_%),%H=%H_","_$P($H,",",2) D %H
   ;
RT   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from: N
   ; called by:
   ; calls:
   ; falls through to: RT1
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   D TT
   ;
RT1   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from: N-RT
   ; called by:
   ; calls:
   ; branches to: E^%DT
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %=$P(%H,",",2) D S S %=X_$S(%:%,1:.24) I %DT'["S" S %=+$E(%,1,12)
   Q:'$D(%(0))  S Y=%
   ;
   GOTO E^%DT ; end of N-RT-RT1
   ;
   ;
PF   ; subroutine short description?
   ; original comment = from %DT
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %H=$H D YMD S %(9)=X,X=%DT["F"*2-1 I @("%I(1)*100+%I(2)"_$E("> <",X+2)_"$E(%(9),4,7)") S %I(3)=%I(3)+X
   ;
   QUIT  ; end of PF
   ;
   ;
T   ; subroutine short description?
   ; original comment = from %DT
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ; falls through to: PM
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   F %=1:1 S Y=$E(X,%) Q:"+-"[Y  G 1^%DT:$E("TODAY",%)'=Y
   S X=$E(X,%+1,99) G PM:Y=""
   I X?1.N1"M" S %H=$H D MONTH G D^%DT
   I +X'=X D DMW S X=%
   G:'X 1^%DT
   ;
PM   ; subroutine short description?
   ;;public?;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from: T
   ; called by:
   ; calls:
   ; branches to: D^%DT
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S @("%H=$H"_Y_X) D TT G 1^%DT:%I(3)'?3N
   ;
   GOTO D^%DT ; end of T-PM
   ;
   ;
TT   ; subroutine short description?
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by:
   ; calls:
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   N %M,%D,%Y D 7 S %I(1)=%M,%I(2)=%D,%I(3)=%Y
   ;
   QUIT  ; end of TT
   ;
   ;
END   ; end of routine DIDTC/%DTC

_________________
Frederick D. S. "Rick" Marshall, VISTA Expertise Network, 819 North 49th Street, Suite 203, Seattle, Washington 98103, (206) 465-5765, rick dot marshall at vistaexpertise dot net
"The hidden harmony is best." - Heraclitus of Ephesus


Top
Offline Profile  
 
 Post subject: Re: Rick Refactors %DTC, Step by Step
PostPosted: Tue Dec 07, 2010 4:00 am 

Joined: Tue Dec 07, 2010 3:45 am
Posts: 26
Real Name: Sam Habiel
Began Programming in MUMPS: 02 Dec 2006
Hi Rick,

I finally read this. Good job.

One thing I found missing which really helps me: I got to writing Unit Tests which I run periodically while I refactor so that I would know whether I broke something while refactoring. I would share an example, but the trac server where I keep the Scheduling GUI code is down right now for maintenance.

What typically gets me is forgetting to put a dot in anonymous functions comments and thus upsetting the flow.

One example I just dealt with yesterday: I am changing error traps from old style
Code:
S X="ETRAP^BSDX00" X ^%ZOSF("TRAP")

to M95 style:
Code:
N $ET S $ET="ETRAP^BSDX00"

Only that wouldn't work: it needs to be
Code:
N $ET S $ET="G ETRAP^BSDX00"

Running the Unit tests helps me make sure that the routine I am refactoring still works. Due to the nature of %DTC, it is easy to create a unit test suite for it.

Sam


Top
Offline Profile  
 
 Post subject: An Aside on Unit Testing
PostPosted: Tue Dec 07, 2010 9:04 pm 
Site Admin
User avatar

Joined: Mon Nov 01, 2010 1:58 pm
Posts: 205
Location: Seattle, Washington
Real Name: Frederick D. S. "Rick" Marshall
Began Programming in MUMPS: 15 Jun 1984
I'm glad you're enjoying the thread, but it still has a long way to go.

As for unit testing, it is very helpful, but only some routines lend themselves readily to it. You're right that %DTC is a pretty good example. Most of its subroutines lend themselves to unit testing. As we will see, though, it contains several exceptions, subroutines that aren't actually subroutines but are bleeding hunks of %DT transplanted over here. Those would be exceptionally hard to accurately unit test, since they do not and really should not contain cleanly defined interfaces; the proper solution for them is to reinsert them back into %DT where they go, or to split them along with additional code from %DT into a separate routine (%DT1?) - at any rate to reconstruct the whole they're a part of until you have something that can have a cleanly defined interface.

Joel Ivey wrote a tool to deconstruct a routine into parts that can be unit tested. The results illustrate a painful but necessary truth, that good things conflict with each other. Code that's optimized for clarity and maintainability is not optimized for unit testing and vice versa. There is an overlap area - when routines naturally contain lots small subroutines as is the case with function libraries - but there is a larger area in which optimizing a routine for unit testing means making it less maintainable.

To a primary developer - an original author and maintainer - this concept might seem paradoxical, since testing is one of the hardest parts of primary development. To a secondary developer - an extender and manager - who later has to troubleshoot this code in the middle of the night when it breaks, the difference between easy-to-test and easy-to-comprehend can be the difference between promoted and fired. Optimizing for secondary developers, not primary ones, is one of the secrets to writing maintainable code because secondary developers are more demanding readers with more at stake.

Some things about software are easiest to understand when it is deconstructed into parts - like proving "correctness" - but other things are easiest to understand when it is reassembled into a whole - like how all the parts work together. In the modern era we have a reflex toward atomism, toward deconstructing everything in the belief that it makes things simpler, but that's only a half-truth. The more complex and integrated a piece of software is, the less sense you can make of it as parts and the more you need to understand the flow of control as a whole. True understanding cannot come from analysis, only from the combination of analysis and synthesis.

For example, in complex code, top-down, structured-programming techniques make it very easy to see the order in which things happen and how the parts are nested. When such code is smashed into pieces because of a reflexive preference for small subroutines, the results can be absolutely indecipherable code. I have seen in certain VISTA packages where one large, clear subroutine has been replaced by fifty small subroutines - which no one calls but themselves - whose relationships are now impractical for anyone but the original author to work out. This is especially true when complex nested loops, recursion, and indirection are combined, as frequently happens in the infrastructure and CPRS packages.

In other words, it does no good to write simple, short, clear subroutines if by doing so you obscure all the relationships among them that used to be explicit. That creates a kind of pseudo-clarity in which anyone looking at the code thinks it's simple but when they have to support it they find they can't.

The principle I try to follow is Socrates's exhortation to do justice to the issues at hand. Let the shape of the software tell you whether it's better handled by decomposing it into subroutines or by integrating it into one large routine. In this case that would be applied with these two guidelines:

1) If the relationships among the parts are very simple - and above all if the subroutines have enough meaning on their own that they are used by multiple modules or packages - then decomposition is the way to go, because when you obscure the relationships you don't lose as much as you gain.

2) If the relationships are complex, if the flow of control is intricate, then to make the code maintainable you need those relationships to be foregrounded by using nested blocks and top-down structures so you have the physical geometry of the page helping the reader comprehend that flow and those relationships.

Unit testing can be done on complex, integrated routines, of course, but it's a very different kind of discipline than people usually think of when hear "unit testing." That phrase in most people's minds is necessarily tied to an extreme degree of modular decomposition, which as I've argued is only appropriate for some algorithms and not others. When it's not suitable we don't have to abandon unit testing, just change our approach to it so we can create more complex, sophisticated unit tests for evaluating necessarily complex, sophisticated routines.

When we reach journeyman status as a programmer in any language, we know enough to be smart and powerful, but we haven't forgotten and internalized enough yet to be wise - it's when the best habits and judgment become internalized and largely unconscious that we free up the cognitive real estate to have room for taste and discrimination without sacrificing speed - so we try to make up for our lack of wisdom through the strict application of processes and procedures. The problem, as Gödel pointed out is that no such system will always be right - sometimes we'll make things better and sometimes worse. By always following a rule we may make ourselves faster and more consistent, but we retard our learning process and we make mistakes through our overconsistency.

The solution is not to try so hard to become so fast so quickly. It's better to wrestle with each new piece of software until we understand its flow of control well enough to decide whether or not it makes an ideal candidate for radical decomposition into subroutines. That is the struggle in which we can develop our taste for when decomposition or any other process is or is not called for. This approach to development will be slower in the beginning, but we will end up both faster and more accurate in the end.

As Emerson wrote, "a foolish consistency is the hobgoblin of little minds," a somewhat insulting expression that in truth applies to every mind during the process of grappling with things we have learned but not yet mastered, for adolescence and other journeyman states. By contrast, mastery leads us not to consistency but to an overall inconsistency made up of conflicting patterns of localized consistency - the realm of nature and of taste and craftsmanship.

With those caveats, I agree most of %DTC is well-suited to unit testing. I invite anyone who wants to join in this little volunteer project to develop unit tests for the documented entry points, and then later as we sort out what the private subroutines are supposed to do to add unit tests for them, too. If I get any takers on that invitation, I'd suggest you start a separate thread in this forum to discuss unit testing of %DTC.

Postscript: Your example of refactoring error processing is well chosen. In the months ahead we'll start some more threads on advanced topics, including transaction processing, error processing, and their interactions. As we're discovering, transitioning from vendor-specific error processing to standard error processing is considerably more involved than is suggested by that example, but that example is enough to underscore your point - how easy it is to make mistakes during refactoring, and the importance of unit testing.

_________________
Frederick D. S. "Rick" Marshall, VISTA Expertise Network, 819 North 49th Street, Suite 203, Seattle, Washington 98103, (206) 465-5765, rick dot marshall at vistaexpertise dot net
"The hidden harmony is best." - Heraclitus of Ephesus


Top
Offline Profile  
 
 Post subject: Re: Rick Refactors %DTC, Step by Step
PostPosted: Wed Dec 08, 2010 12:03 am 

Joined: Tue Dec 07, 2010 3:45 am
Posts: 26
Real Name: Sam Habiel
Began Programming in MUMPS: 02 Dec 2006
Rick, here are my unit tests from BSDX26.m

Code:
UT ; Unit Tests
    ; Test 1: Make sure this damn thing works
    N ZZZ
    N %H S %H=$H
    N NOTE S NOTE="New Note "_%H
    D EDITAPT(.ZZZ,188,NOTE)
    I ^BSDXAPPT(188,1,1,0)'=NOTE W "ERROR",! B
    ; Test 2: Test Errors -1 and -2
    N ZZZ
    N NOTE S NOTE="Nothing important"
    D EDITAPT(.ZZZ,"BLAHBLAH",NOTE)
    I +^BSDXTMP($J,1)'=-1 W "ERROR IN -1",! B
    D EDITAPT(.ZZZ,298734322,NOTE)
    I +^BSDXTMP($J,1)'=-2 W "ERROR IN -2",! B
    ; Test 4: M Error
    N bsdxdie S bsdxdie=1
    D EDITAPT(.ZZZ,188,NOTE)
    I +^BSDXTMP($J,1)'=-100 W "ERROR IN -100",! B
    k bsdxdie
    ; Test 5: Trestart
    N bsdxrestart S bsdxrestart=1
    N %H S %H=$H
    N NOTE S NOTE="New Note "_%H
    D EDITAPT(.ZZZ,188,NOTE)
    I ^BSDXAPPT(188,1,1,0)'=NOTE W "ERROR in TRESTART",! B
    ; Test 6: for Hosp Location Update
    N DATE S DATE=$$NOW^XLFDT()
    S DATE=$E(DATE,1,12) ; Just get minutes b/c of HL file input transform
    D APPADD^BSDX07(.ZZZ,DATE,DATE+.001,3,"Dr Office",30,"Old Note",1)
    N APPID S APPID=+$P(^BSDXTMP($J,1),U)
    D EDITAPT(.ZZZ,APPID,"New Note")
    I ^BSDXAPPT(APTID,1,1,0)'="New Note" W "Error in HL Section",! B
    I $P(^SC(2,"S",DATE,1,1,0),U,4)'="New Note" W "Error in HL Section",! B
    QUIT


Top
Offline Profile  
 
 Post subject: Re: Rick Refactors %DTC, Step by Step
PostPosted: Mon Jan 03, 2011 9:55 am 
Site Admin
User avatar

Joined: Mon Nov 01, 2010 1:58 pm
Posts: 205
Location: Seattle, Washington
Real Name: Frederick D. S. "Rick" Marshall
Began Programming in MUMPS: 15 Jun 1984
Now that the holidays are over, I'm looking forward to resuming the refactoring of %DTC.

_________________
Frederick D. S. "Rick" Marshall, VISTA Expertise Network, 819 North 49th Street, Suite 203, Seattle, Washington 98103, (206) 465-5765, rick dot marshall at vistaexpertise dot net
"The hidden harmony is best." - Heraclitus of Ephesus


Top
Offline Profile  
 
 Post subject: Rick Refactors %DTC, Step Four
PostPosted: Mon Jan 03, 2011 7:51 pm 
Site Admin
User avatar

Joined: Mon Nov 01, 2010 1:58 pm
Posts: 205
Location: Seattle, Washington
Real Name: Frederick D. S. "Rick" Marshall
Began Programming in MUMPS: 15 Jun 1984
Step four is about documenting calls, branches, and pipes. As a side effect, it's also the first and most aggressive step in converting horizontal code (many commands per line) into vertical code (few commands per line).

1) One subroutine at a time, we convert lines made up of multiple commands into multiple lines made up of one or few commands. Where commands have multiple arguments, we break them up into separate commands - especially set and do, but not if commands. We break this rule wherever it would alter the flow of control - mainly conditions and loops. We do this so that it is easier to spot subroutine calls - because we pull them up to the front of their own lines wherever possible - and to leave more room for commenting the calls, since they have such a large impact on what a given subroutine is about.

2) To preserve the logical grouping of commands previously accomplished by having multiple commands per line, we insert blank comment lines to create "paragraphs" that roughly correspond to the old lines. Sometimes the grouping of commands per line in horizontal MUMPS is arbitrary, but often there is a real logic to it that helps the reader make sense of relationships among the commands. The purpose of this step is to preserve those visual relationships in a new way.

3) For every do or goto command, add a comment following it to explain what the call or branch does. Where the target is a private subroutine, we don't yet know what it does, so leave the comment blank for now. Where the target is public, copy the short subroutine description from the public label's comment; we can refine those later once we understand the flow of control around the call, but for now it helps us begin building a picture of how the subroutine works.

4) For each call, add the target to the calls comment line, and add the caller's label to the target's called by comment line. This begins the work of building up the documentation on the web of relationships among the subroutines. Not until we finish that documentation will we truly know which subroutines are public (and hence must be kept) and which are private (and so could be renamed, moved to other routines, or eliminated as we choose). This is a crucial step in working out the articulation of the software's joints.

5) Don't treat goto commands as calls. They are too different, and can create code with a radically different kind of structure than do commands can. Instead, call them branches, and add header-comments as needed to document where we branch to and from.

6) Do routine searches on a complete VISTA system to locate all calls into the %DTC's "private" subroutines. There are two reasons to do this.

First, in the early days VISTA programmers often established verbal agreements to publicly support calls to a subroutine, and not all of these verbal agreements have been captured in the documentation. That is, you can't be 100% sure the documentation of supported entry points is complete. For example, there are so many calls to DOW^%DTC that I suspect it of being an undocumented public subroutine.

Second, in the early days some VISTA programmers didn't understand the importance of distinguishing between public and private calls, so they read each other's code and called private subroutines whenever they found something useful. This extremely dangerous practice is largely a thing of the past now, but you can still find examples left over in VISTA code. For example, the two calls to TT^%DTC from the Imaging package initially fit the profile, so I suspect PMK at the Washington ISC went fishing for code, discovered TT, and decided to call it without asking.

Now that these unexpected calls to private subroutines form outside Fileman have been documented, they will need to be researched to learn why they exist and what to do about them. More importantly, now we know that the called subroutines cannot be changed without breaking code in other packages, which is the point of this step.

7) Do global searches on a complete VISTA system to locate all calls into the %DTC's "private" subroutines from the database. Most developers skip this step when researching or refactoring their code, but it is dangerous to skip this step. Note the two calls to D^%DTC we found in the database, one from a field definition, one from a function definition. I have not yet finished this step, because I have not yet run my search against the new VISTA master-calling index, which documents all calls, even those buried in obscure parts of the database. Between now and step five, I'm going to finish writing the first draft of my master-calling index program so I can run a complete search of calls against it (and once the first draft's done I'll release it to the community). What I've annotated in %DTC for this step is just the results of a routine search of all routines and global search of the ^DD global.

8) When you finish annotating all the calls and branches, look over your subroutines for any that are not called but are only branched to from a different routine and that upon completion branch back to that routine. Then go review the labels they branch to and from in that other routine. In some cases, in older code, what you'll find is what I call a pipe - a section of code that was moved out of the original routine because of routine-size limits in the old days. Such code is really not a subroutine at all; it is a bleeding hunk of the other routine that really ought to be moved back into the other routine to help make the code more maintainable and comprehensible.

Analysis revealed two pipes in %DTC: N-RT-RT1, which is part of NA^%DT, and T-PM, which is part of 8^%DT. It is crucial before we get into phase two of refactoring (variable scoping) that we sort out which chunks of code are subroutines (which scope their variables in predictable ways) and which are pipes (which may not make any sense at all outside the context of the rest of the code they belong with) so you don't bang your head against the wall in frustration later.

9) For every label left with an empty calls or called by header comment, delete the comment line in the interests of concision, to help improve the signal-to-noise ratio.

10) For every label called only by routines in the current package (Fileman), change its header comment to private. This is a little premature, since we haven't yet used the master-calling index to get an authoritative list of calls into the routine, but we're already close enough for a good approximation, and we can fix any mistakes on this later.

Here are the results of step four:
Code:
%DTC   ;SFISC/GFT,XAK,VEN/TOAD - Date/Time Library ; 01/03/2011 6:54pm
   ;;22.0;VA FileMan;**14,36,71,117,1035**LOCAL**;Mar 30, 1999
   ;Per VHA Directive 10-93-142, this routine should not be modified.
   ;
   ; about the ^%DTC entry point:
   ;
   ; returns the number of days between two dates
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; falls through to: D, which is possibly private
   ;
   ; input:
   ;   X1 = first date in Fileman format (not returned)
   ;   X2 = second date in Fileman format (not returned)
   ; output:
   ;   X = # days between the two dates. X2 is subtracted from X1.
   ;   %Y = 1 if both dates have both month and day values
   ;        0 if the dates were imprecise and therefore not workable
   ;
   ;
   ; PUBLIC ENTRY POINTS
   ;
   ; ^%DTC = returns the number of days between two dates [& label D]
   ; C^%DTC = takes a date and adds or subtracts a number of days
   ; COMMA^%DTC = delimit long numbers with commas
   ; H^%DTC = converts a Fileman date/time to $H format [& label TOH]
   ; DW^%DTC = like H^%DTC but also returns name of day of week
   ; HELP^%DTC = displays a date/time help prompt [& label 0]
   ; NOW^%DTC = returns the current date/time in VA Fileman and $H formats
   ; S^%DTC = converts a $H format time to a Fileman format time
   ; YMD^%DTC = converts a $H format date to a Fileman format date
   ; YX^%DTC = converts a $H date/time to printable date/time (& FM format)
   ;
   ; NOTE: don't forget to check integration agreements for additional,
   ; private references from other packages prior to removing or
   ; otherwise fundamentally changing any of the "private" entry points.
   ;
   ; possibly private entry points:
   ;
   ; 7
   ; %
   ; %H
   ; DMW
   ; DOW
   ; $$LEAP
   ; MONTH
   ; N-RT-RT1
   ; PF
   ; T-PM
   ; TT
   ;
   ; **LOCAL MOD** change history
   ;
   ; 2010 06 11 VEN/TOAD: Rick Marshall began refactoring the routine as
   ; a Paideia exercise after accepting John Leo Zimmer's challenge on
   ; Hardhats. I refactored it in the following steps:
   ;
   ; 1  clearly separate the subroutines & identify their end points
   ;
   ; 2  insert subroutine header comments, save original comments; create
   ;    contents list
   ;
   ; 3  2010 11 14: document public entry points and reorganize routine
   ;    around them: answer questions public, procedure, falls through
   ;    from, and falls through to; delete called by for public entry
   ;    points; for public entry points answer input, output, throughput,
   ;    and subroutine short description; move public entry points to top
   ;    and in the order they appear in the documentation; alphabetize
   ;    remaining possibly private entry points
   ;
   ; 4  2011 01 03: fill in calls for all subroutines and called by for
   ;    all private ones; isolate all calls onto their own lines so they
   ;    can be commented; convert from horizontal code to vertical to
   ;    make analysis and commenting easier and add blank lines to
   ;    more or less preserve command groups; add branches to and from
   ;    comments where GOTO is used; add pipe comments; delete calls &
   ;    called by if n/a
   ;
   ;
D   ; body of the ^%DTC entry point
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from: ^%DTC/DIDTC, which is public
   ; called by: DAYS^FBCHACT0, TRANS^FBCHSTA, RPT1^MPIFNQ,
   ;   INACTIVE^SCMCTSK1, NIGHT^SCMCTSK2, EN^XQ93, MUMPS Code field (1) of
   ;   Function file (.5) entry DGFYD, Input Transform of Fiscal Year Date
   ;   field (.91) of Census subfile (41.91) of Census file (41.9)
   ; calls: H
   ;
   N %T
   I 'X1!'X2 D  Q
   . S X=""
   . S %Y=0
   ;
   S X=X1
   D H ; converts a Fileman date/time to a $H format date/time
   S X1=%H
   ;
   S X=X2
   S X2=%Y+1
   D H ; converts a Fileman date/time to a $H format date/time
   S X=X1-%H
   S %Y=%Y+1&X2
   ;
   K %H,X1,X2
   ;
   QUIT  ; end of ^%DTC/DIDTC-D
   ;
   ;
C   ; takes a date and adds or subtracts a number of days
   ;;public;procedure;clean?;interactive?;SAC-compliant?;recursive?
   ; calls: H, YMD
   ;
   ; input:
   ;   X1 = date in Fileman format (not returned)
   ;   X2 = # days to add (positive) or subtract (negative) (not returned)
   ; output:
   ;   X = resulting date in Fileman format
   ;   %H = resulting date in $Horolog format
   ; If input includes time, so will output.
   ;
   N %,%T,%Y
   S X=X1
   S X2=+$G(X2)
   I 'X D  Q
   . S X=""
   . S %H=""
   ;
   D H ; converts a Fileman date/time to a $H format date/time
   S %H=%H+X2
   D YMD ; converts a $H format date to a Fileman format date
   S:$P(X1,".",2) X=X_"."_$P(X1,".",2)
   K X1,X2
   ;
   QUIT  ; end of C^%DTC
   ;
   ;
COMMA   ; delimit long numbers with commas
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ;
   ; input:
   ;   X = # to format (e.g., 42.585)
   ;   X2 = # decimal digits to output; shorter decimals will be padded
   ;        with zeroes, longer ones rounded down. Append $ (e.g., "2$") to
   ;        prefix $ in output (e.g., "$42.59". Defaults to 2.
   ;   X3 = length of output. Ignored if less than output length. Defaults
   ;        to 12.
   ; output:
   ;   X =  the formatted #, delimited by commas if long, rounded to #
   ;        decimal digits specified in X2. If X2 contained $ then $ will
   ;        precede leftmost digit. If # was negative, then output will be
   ;        in parentheses. If # was positive, a trailing space will be
   ;        appended. If formatted # is shorter than X3, it will be padded
   ;        with leading spaces to fill out the length.
   ; examples:
   ;   >S X=12345.678 D COMMA^%DTC
   ;    X="  12,345.68 "
   ;   >S X=9876.54,X2="0$" D COMMA^%DTC
   ;    X="     $9,877 "
   ;   >S X=-3,X2="2$" D COMMA^%DTC
   ;    X="     ($3.00)"
   ;   >S X=12345.678,X3=10 D COMMA^%DTC
   ;    X="12,345.68 "
   ; note:
   ;
   S %D=X<0
   S:%D X=-X
   S %=$S($D(X2):+X2,1:2)
   S X=$J(X,1,%)
   S %=$L(X)-3-$E(23456789,%)
   S %L=$S($D(X3):X3,1:12)
   ;
   F %=%:-3 Q:$E(X,%)=""  S X=$E(X,1,%)_","_$E(X,%+1,99)
   ;
   S:$D(X2) X=$E("$",X2["$")_X
   S X=$J($E("(",%D)_X_$E(" )",%D+1),%L)
   K %,%D,%L
   ;
   QUIT  ; end of COMMA^%DTC
   ;
   ;
DW   ; like H^%DTC but also returns name of day of week
   ;;public;procedure;clean?;silent?;SAC-compliant?recursive?
   ; calls: H
   ;
   ; input:
   ;   X = date/time in Fileman format
   ; output:
   ;   %H = date in $H format. If X is imprecise, then the first of the
   ;        month or year is returned.
   ;   %T = time in $H format (# seconds since midnight). If X has no time
   ;        then %T = zero.
   ;   %Y = day-of-week as # from 0 to 6 (0 = Sunday and 6 = Saturday). If
   ;        X is imprecise, then %Y = -1.
   ;   X = name of the day of the week ("SUNDAY", "SATURDAY", etc.). If
   ;        X is imprecise, then X = "".
   ;
   D H ; converts a Fileman date/time to a $H format date/time
   S Y=%Y
   S X=$P("SUN^MON^TUES^WEDNES^THURS^FRI^SATUR","^",Y+1)_"DAY"
   S:Y<0 X=""
   ;
   QUIT  ; end of DW^%DTC
   ;
   ;
H   ; converts a Fileman date/time to a $H format date/time
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; original comment = called from DIG, DIP4
   ; falls through to: TOH, which is possibly private
   ;
   ; input:
   ;   X = date/time in Fileman format (not returned)
   ; output:
   ;   %H = date in $H format. If X is imprecise, then the first of the
   ;        month or year is returned.
   ;   %T = time in $H format (# seconds since midnight). If X has no time
   ;        then %T = zero.
   ;   %Y = day-of-week as # from 0 to 6 (0 = Sunday and 6 = Saturday). If
   ;        X is imprecise, then %Y = -1.
   ;
   I X<1410000 D  Q
   . S %H=0
   . S %T=0
   . S %Y=-1
   ;
   S %Y=$E(X,1,3)
   S %M=$E(X,4,5)
   S %D=$E(X,6,7)
   ;
   S %T=$E(X_0,9,10)*60+$E(X_"000",11,12)*60+$E(X_"00000",13,14)
   ;
TOH   ; part two of H^%DTC
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from: H^%DTC, which is public
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   N DILEAP D
   . N Y S Y=%Y+1700
   . S:%M<3 Y=Y-1
   . S DILEAP=(Y\4)-(Y\100)+(Y\400)-446
   S %H=$P("^31^59^90^120^151^181^212^243^273^304^334","^",%M)+%D
   S %=('%M!'%D)
   S %Y=%Y-141
   S %H=(%H+(%Y*365)+DILEAP+%)
   S %Y=$S(%:-1,1:%H+4#7)
   K %M,%D,%
   ;
   QUIT  ; end of H^%DTC-TOH
   ;
   ;
HELP   ; displays a date/time help prompt
   ;;public;procedure;clean?;interactive;SAC-compliant?;recursive?
   ; calls: %
   ; falls through or branches to: 0, which is possibly private
   ;
   ; input:
   ;   %DT = flags to control the kind of help messages displayed:
   ;         A = Ask for date input
   ;         E = Echo the answer
   ;         F = Future dates are assumed
   ;         I = for Internationalization, assume day # precedes month #
   ;             in input.
   ;         M = only Month and year input is allowed
   ;         N = pure Numeric input is not allowed
   ;         P = Past dates are assumed
   ;         R = Requires time input
   ;         S = Seconds should be returned
   ;         T = Time input is allowed but not required
   ;         X = eXact input is required
   ;   %DT(0) = a Fileman-format date (e.g., 2690720) to allow only dates
   ;         >= that date/time. Set it negative (e.g., -2831109.15) to
   ;         allow only dates <= that date/time. Set it to NOW to allow
   ;         dates from the current time forward. Set it to -NOW to allow
   ;         dates up to the current time. All of these cases will modify
   ;         the help text displayed. Defaults to no range restrictions on
   ;         date/times other than those already specified by %DT.
   ;   See the documentation of entry point ^%DT in the Fileman Programmers
   ;   Manual for a fuller explanation of how these two parameters work.
   ; output:
   ;   the specified date/time help text will be written to the current I/O
   ;   device.
   ;
   S DDH=$S($D(DDH):DDH,1:0),A1="Examples of Valid Dates:" D %
   ;
   I %DT["M" D  G 0 ;
   . S A1="  "_$S(%DT["I":1.1957,1:"JAN 1957 or JAN 57")_$S(%DT'["N":" or 0157",1:"") D %
   . S A1="  T    (for this month)" D %
   . S A1="  T+3M (for 3 months in the future)" D %
   . S A1="  T-3M (for 3 months ago)" D %
   . S A1="Only month and year are accepted. You must omit the precise day." D %
   ;
   S A1="  "_$S(%DT["I":"20.1.1957",1:"JAN 20 1957 or 20 JAN 57")_" or "_$S(%DT["I":"20/1",1:"1/20")_"/57"_$S(%DT'["N":" or "_$S(%DT["I":200157,1:"012057"),1:"") D %
   S A1="  T   (for TODAY),  T+1 (for TOMORROW),  T+2,  T+7,  etc." D %
   S A1="  T-1 (for YESTERDAY),  T-3W (for 3 WEEKS AGO), etc." D %
   ;
   S A1="If the year is omitted, the computer " D  D %
   . I %DT["P" S A1=A1_"assumes a date in the PAST." Q
   . I %DT["F" S A1=A1_"assumes a date in the FUTURE." Q
   . S A1=A1_"uses CURRENT YEAR.  Two digit year" D %
   . S A1="  assumes no more than 20 years in the future, or 80 years in the past."
   . Q
   ;
   I %DT'["X" D
   . S A1="You may omit the precise day, as:  "_$S(%DT["I":1,1:"JAN,")_" 1957" D %
   ;
   I %DT'["T",%DT'["R" G 0 ;
   ;
   S A1="If only the time is entered, the current date is assumed." D %
   S A1="Follow the date with a time, such as "_$S(%DT["I":"20.1",1:"JAN 20")_"@10, T@10AM, 10:30, etc." D %
   S A1="You may enter a time, such as NOON, MIDNIGHT or NOW." D %
   S A1="You may enter   NOW+3'  (for current date and time Plus 3 minutes" D %
   S A1="  *Note--the Apostrophe following the number of minutes)" D %
   ;
   I %DT["S" D
   . S A1="Seconds may be entered as 10:30:30 or 103030AM." D %
   ;
   I %DT["R" D
   . S A1="Time is REQUIRED in this response." D %
   ;
0   ; part two of HELP^%DTC
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; falls through or branches from: HELP^%DTC, which is public
   ; calls: %, DD^%DT
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   Q:'$D(%DT(0))
   ;
   S A1=" " D %
   S A1="Enter a date which is "_$S(%DT(0)["-":"less",1:"greater")_" than or equal to " D %
   ;
   S Y=$S(%DT(0)["-":$P(%DT(0),"-",2),1:%DT(0))
   D DD^%DT:Y'["NOW" ; converts a Fileman date/time to external format
   ;
   I '$D(DDS) D  Q
   . W Y,"."
   . K A1
   ;
   S DDH(DDH,"T")=DDH(DDH,"T")_Y_"."
   K A1
   ;
   QUIT  ; end of HELP^%DTC-0
   ;
   ;
NOW   ; returns the current date/time in VA Fileman and $H formats
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; calls: TT, S
   ;
   ; input: none
   ; output:
   ;   % = Fileman date/time down to the second
   ;   %H = $H date/time
   ;   %I(1) = numeric value of the month
   ;   %I(2) = numeric value of the day
   ;   %I(3) = numeric value of the year
   ;   X = Fileman date only
   ;
   S %H=$H
   S %H=$S($P(%H,",",2):%H,1:%H-1)
   D TT ;
   ;
   S %=$P(%H,",",2)
   D S ; converts a $H format time to a Fileman format time
   S %=X_$S(%:%,1:.24)
   ;
   QUIT  ; end of NOW^%DTC
   ;
   ;
S   ; converts a $H format time to a Fileman format time
   ;;public;procedure;clean;silent?;SAC-compliant;recursive?
   ;
   ; input:
   ;   % = # indicating the number of seconds from midnight
   ;       (e.g., $P($H,",",2) or 44504)
   ; output:
   ;   % = decimal part of a Fileman date (e.g., .122144)
   ;
   S %=%#60/100+(%#3600\60)/100+(%\3600)/100
   ;
   QUIT  ; end of S^%DTC
   ;
   ;
YMD    ; converts a $H format date to a Fileman format date
   ; original comment =  called from DIP5. Documented entry point for converting a date/time %H in $H format into a date (in X) and time (in %) in FileMan internal format.
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; calls: 7, S
   ;
   ; input:
   ;   %H = $H format date/time (not returned)
   ; output:
   ;   X = date in Fileman format
   ;   % = time down to the second in Fileman format (as a decimal)
   ;       If %H does not include time, % = zero.
   ;
   I %H[",0" S %=%H N %H S %H=%-1_",86400"
   ;
   N %D,%M,%Y
   D 7 ;
   S %=$P(%H,",",2)
   D S ; converts a $H format time to a Fileman format time
   ;
   QUIT  ; end of YMD^%DTC
   ;
   ;
YX   ; converts a $H date/time to printable date/time (& FM format)
   ; original comment = called from DIV, etc
   ;;public;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; calls: YMD
   ; branches to: DD^%DT
   ;
   ; input:
   ;   %H = date and time in $H format; time is optional. (not returned)
   ; output:
   ;   Y = date in external format. Will include time in external format
   ;       including seconds if %H contained seconds.
   ;   X = date in Fileman format
   ;   % = time in Fileman format (as a decimal value). If %H did not
   ;       include time, % = zero.
   ;
   D YMD ; converts a $H format date to a Fileman format date
   S Y=X_%
   Q:Y=""
   ;
   GOTO DD^%DT ; end of YX^%DTC, go convert FM date/time to external format
   ;
   ;
7   ; subroutine short description?
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by: YMD, TT, TIME^XUTMTP, T^ZTMKU
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I '%H D  Q
   . S %=""
   . S X=""
   ;
   S %=(%H>21608)+(%H>94657)+%H-.1
   S %Y=%\365.25+141
   S %=%#365.25\1
   ;
   S %D=%+306#(%Y#4=0+365)#153#61#31+1
   S %M=%-%D\29+1
   ;
   S X=%Y_"00"+%M_"00"+%D
   ;
   QUIT  ; end of 7
   ;
   ;
%   ; subroutine short description?
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by: HELP, 0
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I '$D(DDS) D  Q
   . W !,"     ",A1
   ;
   S DDH=DDH+1
   S DDH(DDH,"T")="     "_A1
   ;
   QUIT  ; end of %
   ;
   ;
%H   ; subroutine short description?
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by: N-RT-RT1
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   I '$P(%H,",",2) D  Q
   . S %H=%H-1
   ;
   I $P(%H,",",2)<60&(%DT'["S") D
   . S $P(%H,",",2)=60
   ;
   QUIT  ; end of %H
   ;
   ;
DMW   ; subroutine short description?
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by: N-RT-RT1, T-PM
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %=$S(X?1.N1"D":+X,X?1.N1"W":X*7,X?1.N1"M":X*30,+X=X:X,1:0)
   ;
   QUIT  ; end of DMW
   ;
   ;
DOW   ; subroutine short description?
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by: TIME^ABSVL2, Q1^FHADM21, Q1^FHADM3, Q1^FHADM4, Q1^FHMADM21,
   ;   Q1^FHMADM3, Q1^FHMADM4, SETNODE^FHOMRO1, RM^FHOMWOR, LST^FHORD91,
   ;   Q1^FHORD93, LST^FHORD93, F11^FHORE1A, F1^FHPRC2, Q1^FHPRC8,
   ;   F1^FHPRC9, Q2^FHPRF1, C1^FHPRO1, FHPRO2^FHPRO2, D0^FHPRO2,
   ;   FHPRR2^FHPRR2, GETSED^FHPST2, FHSEL3^FHSEL3, V^PRCGPM, CHKONE^XQ92
   ; calls: H
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   D H ; converts a Fileman date/time to a $H format date/time
   S Y=%Y
   K %H,%Y
   ;
   QUIT  ; end of DOW
   ;
   ;
LEAP(X)   ; subroutine short description?
   ; original comment =  Return 1 if leap year
   ;;private;function;clean?;silent?;SAC-compliant?;recursive?
   ; called by: MONTH
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S:X<1700 X=X+1700
   ;
   QUIT '(X#4)&(X#100)!'(X#400) ; end of $$LEAP, return answer
   ;
   ;
MONTH   ; subroutine short description?
   ; original comment = Add months to current date
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by: N-RT-RT1, T-PM
   ; calls: TT, $$LEAP
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S Y=Y_+X
   D TT ;
   ;
   S %=%I(1)+Y
   S %I(1)=%-1#12+1
   S %I(3)=%I(3)+(%-$S(%>0:1,1:12)\12)
   ;
   S %="31^"_($$LEAP(%I(3))+28)_"^31^30^31^30^31^31^30^31^30^31"
   ;
   I %I(2)>$P(%,U,%I(1)) D
   . S %I(2)=$P(%,U,%I(1))
   ;
   S X=%I(3)_"00"+%I(1)_"00"+%I(2)
   ;
   QUIT  ; end of MONTH
   ;
   ;
N   ; subroutine short description?
   ; original comment = from %DT
   ;;private;function?;clean?;silent?;SAC-compliant?;recursive?
   ; pipe: NA^%DT => N-RT-RT1 => either E^%DT or 1^%DT
   ; calls: %H, MONTH
   ; branches to: 1^%DT
   ; falls through or branches to: RT-RT1
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   F %=2:1 D  Q:"+-"[Y  G 1^%DT:$E("NOW",%)'=Y ;
   . S Y=$E(X,%)
   ;
   I Y="" D  G RT ;
   . S %H=$H
   . D %H ;
   ;
   S X=$E(X,%+1,99)
   ;
   I X?1.N1"H" D  G RT ;
   . S X=X*3600
   . S %H=$H
   . S @("X=$P(%H,"","",2)"_Y_X)
   . S %=$S(X<0:-1,1:0)+(X\86400)
   . S X=X#86400
   . S %H=$P(%H,",")+%_","_X
   ;
   I X?1.N1"'" D  G RT ;
   . S X=X*60
   . S %H=$H
   . S @("X=$P(%H,"","",2)"_Y_X)
   . S %=$S(X<0:-1,1:0)+(X\86400)
   . S X=X#86400
   . S %H=$P(%H,",")+%_","_X
   ;
   I X?1.N1"M" D  G RT1 ;
   . S %H=$H
   . D %H ;
   . D MONTH ;
   ;
   D DMW ;
   G 1^%DT:'% ;
   S @("%H=$H"_Y_%)
   S %H=%H_","_$P($H,",",2)
   D %H ;
   ;
RT   ; subroutine short description?
   ;;private;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through or branches from: N
   ; pipe: NA^%DT => N-RT-RT1 => either E^%DT or 1^%DT
   ; calls: TT
   ; falls through to: RT1
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   D TT ;
   ;
RT1   ; subroutine short description?
   ;;private;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through from: N-RT
   ; pipe: NA^%DT => N-RT-RT1 => either E^%DT or 1^%DT
   ; calls: S
   ; branches to: E^%DT
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %=$P(%H,",",2)
   D S ; converts a $H format time to a Fileman format time
   S %=X_$S(%:%,1:.24)
   I %DT'["S" D
   . S %=+$E(%,1,12)
   ;
   Q:'$D(%(0))
   S Y=%
   ;
   GOTO E^%DT ; end of N-RT-RT1, go ???
   ;
   ;
PF   ; subroutine short description?
   ; original comment = from %DT
   ;;private;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by: 2^%DT, MTH^%DT
   ; calls: YMD
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S %H=$H
   D YMD ; converts a $H format date to a Fileman format date
   S %(9)=X
   X=%DT["F"*2-1
   I @("%I(1)*100+%I(2)"_$E("> <",X+2)_"$E(%(9),4,7)") D
   . S %I(3)=%I(3)+X
   ;
   QUIT  ; end of PF
   ;
   ;
T   ; subroutine short description?
   ; original comment = from %DT
   ;;private;function?;clean?;silent?;SAC-compliant?;recursive?
   ; pipe: 8^%DT => T-PM => either D^%DT or 1^%DT
   ; calls: MONTH, DMW
   ; branches to: 1^%DT, D^%DT
   ; falls through or branches to: PM
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   F %=1:1 D  Q:"+-"[Y  G 1^%DT:$E("TODAY",%)'=Y ;
   . S Y=$E(X,%)
   ;
   S X=$E(X,%+1,99)
   G PM:Y="" ;
   ;
   I X?1.N1"M" D  G D^%DT ;
   . S %H=$H
   . D MONTH ;
   ;
   I +X'=X D
   . D DMW ;
   . S X=%
   ;
   G:'X 1^%DT ;
   ;
PM   ; subroutine short description?
   ;;private;function?;clean?;silent?;SAC-compliant?;recursive?
   ; falls through or branches from: T
   ; pipe: 8^%DT => T-PM => either D^%DT or 1^%DT
   ; calls: TT
   ; branches to: 1^%DT, D^%DT
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   S @("%H=$H"_Y_X)
   D TT ;
   G 1^%DT:%I(3)'?3N ;
   ;
   GOTO D^%DT ; end of T-PM,
   ;
   ;
TT   ; subroutine short description?
   ;;public?;procedure;clean?;silent?;SAC-compliant?;recursive?
   ; called by: NOW, N-RT-RT1, T-PM, MAGDHWS^MAGDHWS, PURGE^MAGDRPC5
   ; calls: 7
   ;
   ; input:
   ; throughput:
   ; output:
   ;
   N %M,%D,%Y
   D 7 ;
   S %I(1)=%M
   S %I(2)=%D
   S %I(3)=%Y
   ;
   QUIT  ; end of TT
   ;
   ;
END   ; end of routine DIDTC/%DTC

_________________
Frederick D. S. "Rick" Marshall, VISTA Expertise Network, 819 North 49th Street, Suite 203, Seattle, Washington 98103, (206) 465-5765, rick dot marshall at vistaexpertise dot net
"The hidden harmony is best." - Heraclitus of Ephesus


Top
Offline Profile  
 
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 11 posts ]  Go to page 1, 2  Next

All times are UTC - 8 hours [ DST ]


Who is online

Users browsing this forum: No registered users and 11 guests


You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot post attachments in this forum

Search for:
Jump to:  
cron
Powered by phpBB © 2000, 2002, 2005, 2007 phpBB Group
Theme created StylerBB.net