Re: Abstract public member variales?



Responding to Guild...

I am beginning to see the problem. It is clear to me now that one
part of the problem lies in requirements specification. (B) is
poorly formed because it is not logically indivisible. (The
notion of 'logical indivisibility for requirements specification
is much less flexible than that of OO development, though.) So
there are actually three distinct requirements here:

(A) you must go to the store
(B) you must buy milk
(C) (B) is dependent on (A)


Whether we are talking about a single divisible requirement or a group of indivisible requirements has no effect on what I am trying to say. I am sure that it is not part of the problem.

If requirements must be indivisible then the ordering I described must be applied to sets of requirements instead of individual requirements. I hope the extension to sets is obvious.


There are three fundamental rules of requirements specification:
each requirement must be atomic, unambiguous, and testable. I'm
afraid I have never seen your notion of specificity in any
methodology for eliciting, analyzing, or specifying requirements.


That is probably because it was implicit. Requirements will always be specified either generally or specifically, and I was merely trying to make it perfectly clear by spelling it out in a way that would likely never be necessary in practice. Unfortunately, despite my careful explanation, there are still misunderstandings. I have no clear way to distinguish my meaning of 'requirement' from yours; they are very subtly different but that difference seems very important now.

Nonetheless, any book on managing requirements will describe the basic structure and methodology I described above. I am not making this stuff up as I go along; it is basic software engineering.

Suppose there was a piece of software that deals with the
customers of some company and it needs a function that finds a
list of the customers who demand a certain product:
thoseWhoWant(product).

One possible requirement for this function is that it simply (A) returns a list containing all the relevant customers. That is
rather high in the order of requirements. Another possible
requirement would be (B) that the function returns the list in
sorted order. Notice that the implementations that satisfy (B)
are a subset of those that satisfy (A), so (B) < (A).

When it comes time to create the function in software, you must
know what the requirement on ordering will be. You have two
different requirements defined in different SRSes for different
applications:

Application A SRS:
(1) return an ordered list

Application B SRS:
(1) return an unordered list

The SRS is poorly formed if the order is not specified because if
list ordering is not specified the requirement is ambiguous.


You seem to be putting a lot of pressure on the customer. What if the customer does not care about the order of the list? Why should the customer specify the order if the order is irrelevant to the problem?

If the customer does not care about the ordering, the customer has to specify that. It is necessary because ordered lists are implemented differently than unordered lists (i.e., they have fundamentally different properties) and the developer needs to know which kind of list to implement. If the customer does not specify whether the list must be ordered or not, then the developer is forced to guess whether ordering is important and that guess could be wrong.

Arguing that the customer would have specified ordering if it was important to the customer so the developer can /assume/ the customer doesn't need ordering only works of the customer and developer already agreed on that rule. [Note that in UML that particular rule is mandated by the notation meta semantics. Any '*' relationship without the "<<ordered>>" qualifier is /defined/ such that ordering is unimportant. (Which doesn't mean that it can't be implemented as ordered; generally nonfunctional requirements will preclude that, though.)]

Hopefully you see that there are often multiple possible
requirements that can be satisfied by a single function. Whenever
there is a requirement A and a requirement B, if B < A then any
function that satisfies B will also satisfy A.

The obvious question is: Which requirement is the one that should
appear in the documentation? I suspect that you would say that it
should be the one that is 'less than' all others because only
then would the developer know exactly how to implement the
function. You would want the requirements to explicity declare
whether the result should be sorted so that there is no room for
further specification.

And my assertion is that you are asking the wrong question. The question should be: Which of the atomic, unambiguous, and testable
requirements in the SRS is this function responsible for
resolving? There is no wiggle room. The SRS defines the
requirements individually at a single level of specificity and the
function either resolves requirement X in some fashion or it
doesn't.


I am not certain how to respond to this. I cannot think of anything that is sure to make my point clear, because it involves directly contradicting what you are saying. Obviously if you believe the above then my merely saying the opposite will have no value for you.

In functional decomposition the SRS gives the requirements for the one main function that is above all others and represents the entire software. The SRS is not the place to look for the requirements of any other function.

This is <one> crux of the problem. What you describe is not the way functional decomposition works. The root node in the tree must satisfy /every/ requirement in the SRS. IOW, it represents the /entire/ solution to the customer's problem. If one did no decomposition at all and wrote a single monolithic Assembly program for that node, that program would have to resolve every single requirement in the SRS.

When one decomposes a node, including the root node, one allocates a subset of the that node's SRS requirements to each child node for resolution. That child node is an extension of the parent in resolving the parent's requirements. At any level of the decomposition, if one stops decomposing then the leaf nodes must resolve all the SRS requirements that have been allocated to them.

The 'functional' in functional decomposition comes from the idea that one is subdividing functionality. But that 'functionality' is the same functionality as in the notion of functional requirements. IOW, a function is defined by its requirements. One cannot subdivide functionality unless one knows what the functionality is and what the functionality is is defined in SRS requirements.

From another perspective: How do you know when the functional decomposition is complete? When all of the SRS requirements have been resolved. How do you know when all the SRS requirements have been resolved? When there are none left to resolve. How do you know there are none left to resolve? When you implementations for all of them somewhere in the tree. But that only happens if one has systematically "walked" all of the requirements down the tree and checked them off as they were implemented until none were left. Any other tree construction approach is basically wishful thinking because one could have overlooked a requirement when constructing the tree. So one can argue that a major goal of functional decomposition is to ensure that no requirements are omitted from the solution.

Bottom line: the only requirements resolved in a functional decomposition are SRS requirements.

[In practice there are requirements that are implicit in domain knowledge because it is simply too tedious to form a good SRS if one must reduce the entire domain(s) to axioms. But that assumes that the developers have adequate domain knowledge. IOW, the requirements are still exist by they are not written down in a formal SRS. So the notion of an SRS becomes somewhat metaphorical.]

The requirements of a function are derived from the needs of the people who call that function. The requirements of the main function are directly in the SRS and the mind of the customer. The requirements of other functions are in other places.

This is a second problem. That view is essentially the procedural view from top-down functional decomposition. The parent node always defines the requirements for its child nodes and it depends upon those child nodes resolving those requirements. That results in the classic Do This call stack. It also reflects the inherent dependency chains in functional decompositions.

That is not the OO view. In the OO paradigm messages are not imperatives; they are announcements (I'm Done) that have no expectations whatsoever about what will happen in response to the message. In the OO paradigm the one has two quite different views of 'client'. One is the collaboration client who sends a message. That client simply raises some condition in the overall solution and announces that the condition now prevails. Those clients have nothing at all to do with defining requirements for the responding method. You Entity falls in this class of client.

The other view of 'client' is the DbC-like notion of a generic client in /any/ collaboration in /any/ valid solution context. That client is a surrogate for the overall problem solution. It knows exactly what the responsibility in hand must contribute to the overall problem solution. That is the client that defines the requirements for a responsibility. That client is what one has in mind when one does problem space abstraction. (In a procedural context the collaboration client and the solution client are the same.)

So what is the corresponding exit criteria for problem space abstraction if one does not have the systematic subdivision of requirements that one had in functional decomposition? One abstracts entities and their responsibilities from the problem space based upon their contribution to resolving requirements. IOW, one essentially takes the SRS to the problem space and "walks" the SRS identifying entities and responsibilities to "own" subsets of requirements until all the requirements are accounted for.

Sine the OO paradigm uses problem space abstraction to organize groups of requirements by objects and responsibilities independently (i.e., requirements are mapped to /intrinsic/ responsibilities), there is no hierarchy and inherent dependency chain in the resolution of requirements. Hence the notion of collaboration client as simply a message sender.

So what's my point? The procedural view is quite intuitive, especially in the computing environment. In fact one can argue that the whole SA/D/P approach was designed to emulate the 3GL procedural abstractions of hardware computational models. However, that view directly led to the hierarchical dependency chains in functional decomposition.

If one keeps the Do This mindset, then it is difficult to stand back and objectively evaluate the maintainability problems with hierarchical dependencies. IOW, if one only views the world in terms of message senders /expecting/ message receivers to do things, the notion of hierarchical dependencies seems quite natural. It's Just The Way Things Are.

Conversely, when one has spent a couple of decades doing Do This and then has been immersed in the I'm Done paradigm for another couple of decades as I have been, it seems blatantly obvious why hierarchical dependencies are Evil (as opposed to simply Not Good). So my difficulties in explaining why hierarchical dependencies are Evil may reside in my being inarticulate about trying to explain something I see as intuitively obvious to someone who sees Do This as intuitively obvious.


I made a mistake earlier in the thread that you have brought to my attention in the quoted post and it is important here. I use the term 'requirements of a function' in two very different ways. In one sense, the requirements of a function are those things which the function must do. I am sure that is the sense which I should restrict myself to and it is the one you expect.

However, a function has a different kind of requirements. The implementation has requirements for each function that it calls. These requirements are just like the ones that the customer has: they do not specify what the customer does; they specify what the customer needs. The implementation requires that each function it calls behaves in a certain way so that the implementation will satisfy its own requirements.

In functional decomposition, the functional requirements on a child node are an exact subset of the functional requirements of the parent node -- by definition. Those requirements describe what the node must do. And the requirements on the parent node come exclusively from the SRS. IOW, I see no difference between the requirements of the first paragraph and those of the second paragraph because...


That is where the requirements come from for most functions in the software, not from the SRS.

We are on different planets here. B-( There is only one set of functional requirements that define what functions should do. They may may be grouped in different ways or originate in different domains but they are bundled in one specification of the application functionality -- the <somewhat metaphorical> SRS. The only other requirements that may be resolved in the software are developer requirements for maintainability and those stemming from the use of particular computing space technologies -- both of which are almost exclusively OOP issues.


Just like the example of the sorting function that calls a 'length' function to find the length of the list that it is sorting. If the customer just wants a list sorted, then the SRS will not say anything about the length of the list, or finding the length. The responsibility of length() is not in the SRS at all; it comes from the decomposition of the sort() function.

The 'length' function was not in the sort example; it was part of a different example for finding an element in a list. Nonetheless, the example just proves my case...

The notion of 'list' in the specification of the 'find' function includes a understanding of termination of the list. The resolution of those requirements on termination was delegated to the 'length' function by 'find'. Thus the requirements that 'length' resolves are a subset of the requirements on 'find'. And those requirements, directly or indirectly, exist in the SRS for 'find'.

Note that the fact that the design expresses the resolution of those termination requirements in terms of a list length rather than, say, an end-of-list terminator really is an implementation detail.

Another context where your view of specificity comes into play is
in defining DbC contracts, particularly as manifested by
interfaces, in reuse situations. Then one wants to express the
contract with the outside world in as general terms as possible so
that the service can accommodate as many clients as it semantics
allows.


Those are interesting examples of categorizations of requirements. I have no interesting comments on this, since I feel that we are in agreement. To aid in bridging any terminology based miscommunications, I will make it clear that when I have been talking about requirements I am talking about all of those things.

They weren't categorizations of requirements. They were categories of specificity. Requirements and specificity are pretty much orthogonal. I think that recognizing that they are different things is part of problem here...

Bottom line: in all three contexts the requirements still need to
be fully specified. One can separate them by development stage so
that not all requirements are dealt with at once. One can recast
them them in more convenient ways (e.g., parametric data) and even
split them up (e.g., instantiation vs. collaboration). And one
should take care to avoid overspecification of requirements. But
eventually they all have to be defined and addressed.


It seems to me that in your posts you have been blurring the line between fully specified and overspecified. Just above you chose the stronger of two requirements and called the other one poorly formed without knowing anything about the problem. Why were you not worried about overspecification then?

I do worry about overspecifying; that is what the notion of 'implementation pollution' is about. However, ambiguity in requirements is a far worse problem than overspecification. If you overspecify, then the application still works, albeit maybe not as well as it might have otherwise. But if requirements are ambiguous, the developer must guess wrong and that can lead to incorrectness, as you suggest in an example below.

The SRS defines requirements at <hopefully> the correct level to avoid both ambiguity and overspecification. Requirements management methodologies are designed to facilitate that, which leads to things like the notion of {atomic, unambiguous, testable}.

Once one has a set or requirements in hand, though, one does not have any wiggle in SA/D/P or OOA/D/P for implementing them. One must be quite specific about exactly which SRS requirements any given function resolves. It doesn't matter whether it is a functional decomposition node or an object behavior responsibility. IOW, one can't say, "I don't care exactly what this not does. I want to think of it at a higher level of abstraction than requirements." The SRS has defined the granularity at which one must think about requirements.

To me your examples of ordering in a list, termination in a findElement function, or handling duplicates in a sort are about requirements specification, not implementation. I assert that knowing whether a list is ordered or not is fundamental to the nature of the list and it must be specified. Similarly, I assert that termination of a list must be defined before one can perform searches on it. Finally, I assert that whether a sort must handle duplicates or not is fundamental to the notion of sorting and must be specified. Failure to specify any of those things will necessarily be ambiguous and can lead to incorrect implementations. IOW, the issue is ambiguity, not overspecification.

[BTW, one of the advantages of UML for OOA/D is that the notation does a fairly good job of discouraging overspecification. For example, in UML the most one can specify about a relationship is:

1 R1 *
[A] -------------------- [B]
<<ordered>>

As a practical matter a collection object will very likely be used to implement the relationship during OOP. But UML discourages one from even thinking about defining a class for the collection. Also note that there is no specification of ascending vs. descending because that sort of tactical design decision is the province of OOP. Similarly, if one uses state machines in the design there will be no EventQueueManager class anywhere in the OOA/D because one may not even need one in a synchronous implementation environment.

Alas, UML also drops the ball. The notions of a 'dependency' relationship or public vs. private are really OOP prerogatives. As a result the MDA profile I use doesn't even include them. Yet such models are still rigorous for full automatic code generation.]

Thus in the benefits example, every requirement of Benefit3 must
be known up front before the functional decomposition or else one
can't do the functional decomposition. The requirements of
GetDBInfo are not some sort of deferred design decision that
clients of Benefit3 don't care about; they are the requirements of
Benefit3 and clients of Benefit3 have every expectation that they
will be fulfilled.


I am not sure if your meaning of 'overspecified' is exactly what mine is, but I will risk suggesting that if the requirements of Benefit3 talk about GetDBInfo then they are overspecified.

The requirement is that Benefit3 use a timely value of base salary for the computation of after tax income. The /implementation/ of GetDBInfo resolves that requirement by implementing the _design decision_ that the enterprise database should be the source of a timely value of base salary. IOW, you are /assuming/ an overspecification of the requirement.


Benefit3 is not a database query function; its purpose is not to calculate a value that correctly reflects the state of the database. The purpose of Benefit3 is to calculate a value that correctly reflects the value of a benefit for an employee. It is likely that accessing the database is the only possible way to fulfill that purpose, but that does not make it a requirement of the function; a different way might present itself in maintenance.

On the other hand, this is an example that you created, so I cannot be sure what the needs of the client are. For example, the client might actually want the value computed by Benefit3 to be incorrect in the event that the database were corrupted. That way the value provided can be used to check the database and it is sure to match a similar value calculated from the database.

If the client were simply calculating benefits then making requirements about the database would be overspecification, but if the client is more closely coupled with the database and actually cares about the state of the database for some reason, then its needs are more specific.

Despite that, I hope you can see how a client might not need or want to know that a database even existed when it is using Benefit3. In that case, the client has no needs involving the database and so it can use a Benefit3 function whose requirements do not specify any database accesses.

Just to amplify a point above... You are using 'client' here in the procedural sense. ComputeAfterTaxIncome and Benefit3 both expect a timely value of base salary to be provided because it is a functional decomposition and they provide the requirements for GetDBInfo. But in an OO context the collaboration client would have no such expectations. However, the requirements that the GetDbInfo responsibility resolved would be the same.


Even if the client does care about the database, it might not care about the specific shape of the database. For example, the client of Benefit3 might not care whether the benefit value is stored in the database directly or has to be computed from a salary value.

You seem to be talking about a situation in which the client specifies every operation of Benefit3, including the database accesses and every formula used. That would mean that the client is operating at the exact same level of abstraction as the Benefit3 implementation, which is possible but not a good idea because it constrains maintenance and forces Benefit3 to be replaced when it should be merely modified.

Exactly! That is how functional decompositions are constructed. The proof of that lies in two things.

(1) What would Benefit3 do if there was no further decomposition below it? Its implementation would clearly have to resolve every requirement currently resolved in ComputeAfterTaxIncome, Compute, and GetDBInfo. That's because its requirements are delegated from its parent node and that parent node depends upon /all/ those requirements being resolved. IOW, those requirements are a subset of Benefit3's requirements.

(2) If ComputeAfterTaxIncome, Compute, or GetDBInfo are modified to meet different requirements, then Benefit3 will no longer work the way it was originally supposed to work.

That chain of requirements delegation is what creates the hierarchical dependencies. The dependencies are inherent in the way the tree is constructed.

However, saying that Benefit3's client operates at the same level of abstraction as Benefit3 is misleading. Each level of the tree represents a different level of abstraction, which is why I contrast "higher level" and "lower level" nodes in the tree. The very nature of delegating requirements means that the higher level nodes are not doing the details.

Your notion of specificity seems to be related to those levels of abstraction. That view is useful for understanding what is going on in an Executive Summary sense. But it is quite orthogonal to the way detailed requirements are actually defined for individual functions. One cannot reliably /construct/ the tree from that view because it is too easy to lose requirements. But decomposing by delegating subsets of requirements requirements avoids that problem.

[In practice one usually employs a hybrid approach. One does a preliminary Big Picture view of a decomposition level to facilitate grouping requirements logically. But before decomposing to the next level down one must be quite sure one knows exactly what subsets of requirements are handled by each sibling node at the current level. More important, one must verify that the union of requirements in those subsets is a complete set of the requirements of the parent node. If examination of the requirements in this fashion indicates inconsistencies, one might have to modify the Big Picture view to change the level partitioning.]

This is exactly the point that I was trying to illustrate when I introduced the sort/length example back in: news:Xns98469C1CCA8A2bguild31425@xxxxxxxxxxxx
(Though then I used the word 'superset' when I should have used 'subset' in one place.)

BTW, these sorts of references are not useful to me. My reader accesses USENET newsgroups directly so I need a time stamp to find a particular message in the thread.

We have a different definition of 'fix'. I mean the change to the
functional logic that actually implements the requirements change.
That is in GetDBInfo alone (i.e., changing the DB query). All
the rest of the changes are done solely because of the chain of
dependencies that are inherent in the /developer's/ functional
decomposition structure. IOW, duplicating the limb has nothing to
do with the functional requirements; it is resolving a developer
problem.

To see that, suppose there was no reuse of ComputeAfterTaxIncome
(i.e., no Benefit1 and Benefit2 clients). Then one would change
GetDBInfo in situ without creating a new function or touching any
other function in any way.


GetDBInfo could theoretically be modified in situ, but if the requirements documentation would have to also change then it would be a dangerous choice. It is wanting to do that sort of maintenance that turns hierarchical dependency into Spaghetti Code, because you would not be safe to make the change in situ unless you know exactly who is calling GetDBInfo and how it is used.

I don't see it as theoretical at all. In practice you must do that to avoid needless duplication or bloat from unreferenced code. It also reduces the scope and the number of changes, which reduces the chance of introducing new defects. And it saves effort for the repair by limiting functions touched (which was the main point in distinguishing between the change to the requirements and the changes to the decomposition infrastructure). Remember there could be a dozen levels between Benefit3 and GetDBInfo that would each have to modified with your approach.

Bottom line: if you do not change it in situ in that situation the programming manager is going to break your thumbs.

When dealing with hierarchical dependencies, unless you like dealing with Spaghetti Code, you must follow requirements with total faith. If a function is required to do something, then you must never violate that or else know that you are only making things worse and breaking your software.

That is where the connection with requirements comes in. If the requirements are weak enough then you will be able to make the change in situ. In fact, you will be required to do so.

I don't see how this has anything at all to do with loose or tight requirements.

(1) All the requirements except the change are exactly the same before and after the requirements change.

(2) All the requirements are delegated exactly the same way before and after the requirements change.

(3) The requirement relevant to the change (obtaining base salary vs. burdened salary) is resolved in exactly the same place before and after the requirements change.

(4) The requirement relevant to the change is resolved only in GetDBInfo.

It seems inescapable to me that GetDBInfo must do something different before and after the requirements change. That's because _its requirements changed_. Why would one leave GetDBInfo as it is and create an entire new limb below Benefit3 rather than simply changing GetDBInfo?

The only valid reason for that is because there is higher level node reuse (ComputeAfterTaxIncome) AND the requirements change does not apply to all clients in the reuse. Then and only then does one have to duplicate the limb. Perhaps more important, most of the changes made in that situation are related to the dependency chain in the functional decomposition, not the change from base salary to burdened salary.

It is perhaps not a likely example, but suppose that the requirements only specified that GetDBInfo extracted the salary information from the database without specifying if that was the base salary or the fully burdened salary. That could be acceptable if the software, like myself, does not distinguish between the two because it did not know about a fully burdened salary.

Suddenly the customer storms into the maintainer's office and demands to know what is wrong with the software. It turns out that in this domain, the word 'salary' means fully burdened salary, so GetDBInfo is producing the wrong value when it gets the base salary. It is not fulfilling its requirements because it is not producing the what the domain considers to be 'salary'.

Of course. That's why ambiguous requirements are Not Good.

One quick in situ modification to GetDBInfo and it is now fulfilling its requirements. If the requirements had specified 'base salary', that change would be dangerous, but thanks to weak requirements it is simple, with little danger of breaking anything and no Spaghetti Code.

That would be fine but there are no weak requirements here. Initially Benefit1, Benefit2, and Benefit3 all /needed/ base salary to be used in their computations. They would each produce incorrect results if any other flavor of salary was used. There is nothing weak about that requirement. After the change Benefit1 and Benefit2 still needed base salary while Benefit3 needed burdened salary. There is no way weak vs. strong helps that situation.

This is actually a major reason why one employs the OO paradigm. The paradigm does a good job of isolating requirements so that
fixes for requirements changes are limited to the place where they
are actually implemented without a lot of peripheral
infrastructure changes.


That is certainly true, but it seems as though you are still being too hard on hierarchical dependency.

It's not me. Blame it on the parade of OO Gurus who developed the OOA/D methodologies. The paradigm is designed to eliminate hierarchical dependencies because most of the maintainability problems associated with traditional SA/D/P code could be directly traced to hierarchical dependencies (and indiscriminate uses of global state variables).

As I explained above, I was misusing the terminology here. The requirements that I was talking about are not the requirements specified for the functions. When I talked about functions having requirements, I meant that they have needs that must be fulfilled by GetDBInfo. Those needs are very different from the requirements that the functions that call GetDBInfo must fulfill.

The first three sentences are essentially a summary of hierarchical dependency; a delegation of requirements. To resolve its requirements, a function depends on what other functions do. But...

I meant 'requirements' in the way that the customer has requirements, not to specify what the customer must do but to specify what the customer needs. I do not usually use the word 'requirements' in this way and will be careful to make my meaning clear in the future by using the word 'expectation' when I mean the needs of a function instead of the specified requirements of that function.

.... the last sentence of the first paragraph and this paragraph seem inconsistent. What other requirements are there for the functions other than the functional requirements of the SRS (i.e., customer requirements)?

Also the distinction between what a customer wants and what a customer needs makes me very nervous. Until the '80s that view was Product Out; the vendors decided what the customers needed and built that. With the late '80s came the notion of Voice of the Customer, where vendors determine what the customers really wanted and built that. The Product Out view has been fully discredited in the manufacturing sector now but it still lingers on in much of the software sector.

That is the core problem of higher level node reuse. Every client
in those limbs depends upon what GetDBInfo does. I thought you had
agreed that the dependency chain exists.


I do agree that the dependency chain exists the the behaviour of a function almost always depends upon the behaviour of the functions that it is called, but that dependency is not total.

For example, the results of calling factorial() with a negative number can probably change without affecting any function that calls factorial(). The reason is that no function has any expectations about the value returned from factorial() when it is given a negative number.

Only in the sense that the result is undefined anyway. However, undefined results are a no-no in software development. Among other things requirements must be testable. So I don't think the example is valid.

Similarly, any change to the behaviour of a function that does not violate its requirements will either have no affect at all on the behaviour of the calling functions, or it will affect the calling functions but not cause them to violate their own requirements.

This sort of specificity is only relevant to things like a service capable of processing a 32-bit input won't break for a 16-bit input. But I don't see that as the issue. There is a subset of very specific requirements from the SRS that any given function in the application must resolve. It doesn't matter if it is a node in a functional decomposition or an object responsibility; those requirements are fixed.

The functions that call GetDBInfo expect to get a salary. The functions that call ComputeAfterTaxIncome have very different expectations: they expect to get the after tax income. Nothing expects or wants ComputeAfterTaxIncome to find the salary of the employee, though that might be necessary in practice.

The client of Benefit3 expects to get a correct result from the benefit computation of Benefit3. The correctness of that result is defined in terms of SRS requirements. The requirements that the client delegates to Benefit3 demand a specific flavor of salary to be used or else the result will be incorrect. The fact that Benefit3, in turn, delegates those requirements to ComputeAfterTaxIncome and, eventually, to GetDBInfo does not change the fact that Benefit3's result is only correct if a certain flavor of salary is used.

Nor does it affect that fact that in a functional decomposition, all of those requirements are also requirements of Benefit3's client. IOW, the /only/ thing Benefit3's client doesn't know is how the requirements it delegates to Benefit3 are subsequently delegated by Benefit3 (if at delegated all).

The maintainer knows that at least one client now requires
burdened salary. But how can either the original developer or
the maintainer know that the new requirements don't apply to all
other clients as well?

That will be given in the domain. If Benefit3 were to change from
using the base salary to using the fully burdened salary, then someone must inform the maintainer of that event. If a similar
change were to happen to other functions, then the maintainer
will discover it by the same means: examining the domain, not the
software.

In practice the maintainer is /given/ a change to implement. That
introduces a high degree of myopia for the maintainer. That's
because if Benefit1 and Benefit2 were also affected, whoever
provided the change would have indicated that.


I agree.


So why should the maintainer even look at Benefit1 and Benefit2 in
the domain?


The maintainer should not have to look at Benefit1 or Benefit2, unless it is decide the structure of the software needs to change so radically that the requirement specification of low-level functions will be altered to no longer match the expectation of those that call them. However, any change that requires that would be so contrary to the design of the software that the software should probably be replaced entirely.

The only way one can avoid looking at Benefit1 and Benefit2 is by /always/ duplicating the limb. That is a no-no because of the redundancy.

Even more to the point, how would the developer know that those
were the only two functions that might be affected in the domain?


If the maintainer deliberately breaks the software by violating requirements of a low-level function, then I doubt that the maintainer cares.

The maintainer in not breaking anything. The maintainer is implementing a requirements change for a higher level node. The relevant requirements happen to be implemented in a lower level node. Things only get broken if there are other reuse clients for which the requirements change does not apply.

To avoid needless redundancy the maintainer /must/ determine (A) whether there are other reuse clients and (B) if so, whether the requirements change applies to them. Determining that was one of the biggest single problems with top-down design maintenance and it tended to lower reliability after maintenance. When there was a lot of node reuse and a lot of duplicated limbs (due to prior maintenance), the number of defects inserted during maintenance approached the number of things fixed during maintenance.

If the developer doesn't bother to determine those things, then the limb must always be duplicated. That redundancy just trades one maintenance reliability problem for another.

The real problem, though, is that the domain has nothing to do
with breaking Benefit1 and Benefit2. They only become broken when
the maintainer fixed Benefit3 because of the reuse of
ComputeAfterTaxIncome _in the original developer's design_.


It is true that the maintainer fixed Benefit3, but that is not exactly what caused the problem. The problem was not a direct result of fixing Benefit3, it was a result of the manner in which Benefit3 was fixed: by breaking GetDBInfo. The maintainer broke GetDBInfo in such a way that it would cause Benefit3 to start working correctly, but broke the rest of the software.

The way to fix Benefit3 that does not involve destroying your software is to redesign the implementation of Benefit3 around the requirements of Benefit3 so that the new implementation will meet them were the old one did not. The new implementation is free safely reuse everything that it can, but only if the requirements of the functions that it reuses match the needs of the implementation.

I understand that is your position. What I am trying to point out here is that just trades one maintenance headache for another but introducing duplication. Clearly if the change applies to all clients or there was only one client, then the duplication would be totally unjustified.

But even if it were justified by reuse clients that didn't share the change, the problem lies in the duplication of intermediate level nodes between Benefit3 and GetDBInfo whose requirements have not changed. The requirements that they actually implement are not changed, yet they are still duplicated in the new limb.

That's why I keep pushing on the point that there are two different sorts of changes. One is the change to the code that actually implements the requirement, in this case GetDBInfo (whether one creates a new function or does it in situ). The other sort of change is to the developer's design infrastructure. Duplicating the limb for nodes whose undelegated requirements haven't changed is an infrastructure change that is due solely to the dependencies inherent in the functional decomposition hierarchy.

Even if you do violate the requirements, you still do not have
to walk up the lattice. You can determine if you have
introduced a bug simply by comparing the new implementation to
the requirements.

How else would you determine if there were other clients
besides Benefit3 that were affected by the change?

You cannot. But it is safe to assume that there are.

And how do you even know who the other clients are?


My point was that you do not need to know. If you make a change
that causes a function to violate its requirements, then your
reaction should not be to fully assess the damage by finding all
the places in which it causes trouble. Your reaction should be to
undo the damage entirely. It is always an error to cause a
function to violate its requirements.

So you duplicate the limb whether it needs to be duplicated or
not. Aside from the unnecessary effort, the redundancy that
introduces will lead to thumb-breaking.


I find it difficult to understand why you would call it unnecessary effort when you have so carefully described all the problems that result from doing it the other way.

It will be unnecessary if there are no reuse clients. Why would you duplicate the limb if the only client was Benefit3? Even more interesting, what you you do with the original ComputeAfterTaxIncome and GetDBInfo that no longer have a client? B-)

It will be not only unnecessary but silly if all the reuse clients are affected in the same way. Suppose the change to burdened salary applies to all three clients. Would you seriously consider doing each benefit individually and duplicating the limb three times?!? In that case every function in each limb would have exactly the same executable code!

The effort that I see as unnecessary is walking all over the lattice of function calls to find out how many places you have broken your software, and then somehow trying to fix it. Doing it that way turns a fix in one place into a fix in dozens of places and I cannot even imagine how one would deal with that.

I agree that walking was annoying; it is one of the reasons maintaining spaghetti code was a pain and tended to be unreliable. But the walking is necessary if one does functional decomposition with node reuse because one still wants to minimize duplication. That's why one reused nodes in the first place. To minimize duplication one must determine if other clients are broken and that requires walking.

The last sentence is a real mind boggler. B-) The thing I have been beating on since the very beginning is that hierarchical dependencies result in changes in multiple places that would not be necessary if behaviors were properly decoupled. Your proposed duplication of the limb for Benefit3 is exactly that sort of dependency-driven change. You have to provide a NewComputeAfterTaxIncome and modify the implementation of Benefit3 even though the changed requirements are completely encapsulated in GetDBInfo. Worse, you introduce duplicated code for undelegated requirements in NewComputeAfterTaxIncome.

And where do those potential faults come from? They come from
combining the hierarchical dependencies in functional
decomposition with higher level node reuse. IOW, those particular
faults are only possible because of the design structure. Benefit1 and Benefit2 are not broken if there was no sharing of
limbs through reuse. One could change the GetDBInfo function in
the Benefit3 limb without affecting them at all.

[Of course the nodes were reused because without that one has a
quite different problem. Because of the limb duplication, there
is then a whole different set of potential faults whenever the
change /does/ affect all three benefits because of the multiple
redundant edits.]


Both of these problems are eliminated or at least reduced by using proper documentation and having maintainers that read the documentation and live by it. You do not need to have complete duplication (and should not) if you use documentation to make reuse safe.

How? Your own solution involves duplicating the limb even with "proper" documentation! That's not documentation, it is new code written.

Imagine that GetDBInfo has a more descriptive name, such as GetSalary. Now suppose that the benefit computations change so
that the place in the formula that was occupied by the salary is
now occupied by the commission earned by by the employee. Would
you suggest that GetSalary should be altered so that it no longer
returns a salary value but a commission value instead?

This is completely orthogonal for two reasons. The first is that
naming conventions address a developer problem of readability, not
the application structure.


The name of a function is part of its documentation and changing the implementation of a function so radically that it no longer even matches the name of the function is just one example of how ignoring the documentation when making modifications can have unpleasant effects.

None of which changes the fact that the requirements change is implemented in the function, the function is located in a particular place in the decomposition hierarchy, and that the hierarchy implements a dependency chain. The name of the function or its original documentation does not change the fact that the relevant requirements are encapsulated in the function and that the structure of the functional decomposition places major constraints on how any change is made.

If you choose what a function is supposed to do and stick with
it, you do not get embarrassing mis-documentation situations like
that.

The requirements for any function in a functional decomposition
depend on what requirements the parent function delegates to it. If those requirements change, the function of that node in the
decomposition tree needs to change.


I agree with that.

I honestly can't imagine how you could agree with this and yet not see the problems that such a hierarchical dependency chain creates.

Taxs have a tendency to change. If you specify a particular
formula then you have a problem when the formula changes, since
the specification for the function must change and any other part
of the design that might have depended upon that specification
must be re-examined.

How do you implement the formula without knowing what it is???


You do not need a formula to be specified as a requirement just to know that the formula exists and what it means.

You should be careful to limit the requirements of a function or object to only those things which are actually required. The specific formula is not important, all that is important is that the function produce the correct value. If the function starts producing the wrong value do to a change in the IRS, then maintenance is needed, but not maintenance to the requirements of the function, merely to the implementation which has gotten out-of-date.

I'm jumping up an down screaming again. The formula is not implementation or some arbitrary design decision! It is a specified requirement in the SRS! Any specification of Benefit3 is ambiguous without specifying the formula.

For example, you must know the formula just to decompose the function. The computation of after tax income is only one piece of the formula that Benefit3 needs to compute. How can you know that ComputeAfterTaxIncome or GetDBInfo are needed or what they do if you don't know what that formula is?!?

On the other hand, if you simply specify that the 'correct'
formula be used, without saying what that formula is, then the
maintainer is free to update that formula as an implementation
detail of the function.

And how does the developer know what the 'correct' formula is for Benefit3? As soon as you identify 'compute Benefit3' as the functionality, you have to know how Benefit3 is computed compared
to Benefit1 and Benefit2.


That is right, but just because the developer needs to know how to compute Benefit3 does not suggest that the way to compute it is a requirement of the function. The requirements say what must be done, not how it must be done, with the possible exception of performance requirements which still only say how long it is allowed to take.

Knowing what 'correct' means is not the same as knowing how the computation is implemented. The computation for benefits is fully specified in the problem space and that specification is a requirement on What Benefit3 must do. If you want to complain about overspecification, take it up with the IRS, FASB, the Board of Directors, or whoever defines what such requirements are. But a problem space requirement is a problem space requirement is a problem space requirement...

In this case that requirement is going to be pretty specific so the choices about How to implement it are going to be limited to things like using doubles vs. ints. (However, as discussed above, the decision to use the database has nothing to do with the formula specification.) But that isn't relevant. The level of specification of the requirement is a pure problem space issue.

It sounds like we are talking about the requirements for a
complete piece of software. The requirements in that case are no
different from any other requirements that I have been talking
about and I use the term 'requirement' there in exactly the same
way.

And what is a functional decomposition of an application but a systematic delegation of /all/ of the requirements? The top node
in the tree necessarily resolves all of the application's
requirements.


That is true about the top node, but a function does not delegate its own requirements. A function delegates its implementation to other functions and in doing so it creates expectations about what those function will do. Those expectations become the requirements of the new functions. The expectations do not have to be any part of the SRS requirements, they only have to somehow aid in fulfilling the SRS requirements.

Each higher level node in a functional decomposition delegates at least some of its requirements to lower level nodes. [If all requirements were delegated by all higher level nodes, there wouldn't be a problem because all the requirements would implemented in leaf nodes. That would essentially flatten the tree and allow a peer-to-peer thread through the leaf nodes without implementing the hierarchical structure. (IOW, the decomposition could be thrown away once the leaf nodes and the thread sequence were identified.)]

As for the rest, it is in direct contradiction with your agreement above that parent nodes delegate requirements to child nodes in a functional decomposition. The expectations resulting from that delegation ARE the SRS requirements. The requirements of any child node are a subset of the requirements of the parent node -- by definition. That's why correct results from invoking a higher level node depends on what the lower level nodes do.

I am just citing examples here to underscore that point that any
computation as complex as an employee benefit is very likely to
involve a whole passle of detailed requirements. They may be
implicit in standards or whatever, but they still exist.


I am sure you are correct about that. However, the number of actual requirements is only a matter of scale, it does not affect the underlying design or how it should be maintained.

I don't see any scale unless you mean the requirements on a function are a subset of a larger set of requirements. A requirement is a requirement is a requirement...

That's pretty much what top-down developers did; they documented requirements only in the procedures where they were actually
implemented and left the higher level nodes as information-free
specs like "compute Benefit 3".

This is very central to what I have been talking about in this thread. You see that specification as information-free, when in
fact it provides exactly the information that it should provide.
Of course, in a realistic example the benefit would have a name,
not just 'Benefit 3', but otherwise that is perfect documentation
for the function.

It says exactly what the function is really intended to do an no more. You can use that documentation to decide whether you want
to call that function in any new function you write, based on
whether you need the value of that benefit. You can also use it
to debug the Benefit3 function by checking that the function
actually computes the benefit that it is supposed to compute.

In fact, if so many top-down developers did it that way, then
perhaps they had some good reasons for doing it.

They did it to avoid redundancy.

The fact remains that the set requirements for Benefit3 is a union
of the requirements for ComputeAfterTaxIncome, Compute, and
GetDBInfo. That's because the requirements for those functions
were derived from the requirements of Benefit3 through functional
decomposition and those functions are extensions of Benefit3 by
virtue of the functional decomposition.


Is that only true of your example, or is it the case also for the sort() function that makes use of a length() function call as part of its sorting algorithm? If so, what part in the requirements for
sort() could length() play?

It is true of all top-down functional decomposition. That is the way the tree is constructed; through the delegation of requirements. (I dealt with the length() example earlier.)

At this point I think I have shot my wad in trying to explain why hierarchical dependencies are Evil. It is pretty clear at this point that I have not gotten through. I don't know how to explain it any better. So all I can do is recommend doing a literature search and see if someone else explains it better because I am definitely not making this stuff up as I go along.

IOW, I think it is time to agree to disagree and close down the thread.

--

*************
There is nothing wrong with me that could
not be cured by a capful of Drano.

H. S. Lahman
hsl@xxxxxxxxxxxxxxxxx
Pathfinder Solutions
http://www.pathfindermda.com
blog: http://pathfinderpeople.blogs.com/hslahman
"Model-Based Translation: The Next Step in Agile Development". Email
info@xxxxxxxxxxxxxxxxx for your copy.
Pathfinder is hiring: http://www.pathfindermda.com/about_us/careers_pos3.php.
(888)OOA-PATH



.



Relevant Pages

  • Re: UML questions
    ... "Specifies that the relationship represents an operation, method, or ... > there is an accepted slang that allows dependency to be used where ... the use of association is "slang" and the use of dependency ... closer to the UML specification. ...
    (comp.object)
  • Re: graph of behavior - was Re: State vs. Data (was Re: Fans of Template Method with protected varia
    ... does not the message flow down the dependency to IBar ... system by imposing a god layer that must depend on all lower ... I assume the client is missing becuase there is no mechanism in your ...
    (comp.object)
  • Re: graph of behavior - was Re: State vs. Data (was Re: Fans of Template Method with protected varia
    ... does not the message flow down the dependency to IBar ... system by imposing a god layer that must depend on all lower ... I assume the client is missing becuase there is no mechanism in your ...
    (comp.object)
  • Seeking migration advice
    ... with the workstation Windows environment. ... User's can import any document type into ... We are contemplating a move to .NET, and a conversion of our Client / Server ... Given our dependency on Win32 at the client though, ...
    (microsoft.public.dotnet.languages.vb)
  • Seeking migration advice
    ... with the workstation Windows environment. ... User's can import any document type into ... We are contemplating a move to .NET, and a conversion of our Client / Server ... Given our dependency on Win32 at the client though, ...
    (microsoft.public.dotnet.framework.windowsforms)