Path: csiph.com!usenet.pasdenom.info!aioe.org!.POSTED!not-for-mail From: "Rod Pemberton" Newsgroups: comp.lang.misc Subject: Re: A good subset of C Date: Sat, 17 May 2014 19:58:32 -0400 Organization: Aioe.org NNTP Server Lines: 183 Message-ID: References: <1jy1ogca55286$.hxrf634zi430$.dlg@40tude.net> <127sttidjvix.13z7pcgnrk5ay.dlg@40tude.net> NNTP-Posting-Host: CNsg4fVcCsvs3UaOgZtQCw.user.speranza.aioe.org Mime-Version: 1.0 Content-Type: text/plain; charset=iso-8859-1; format=flowed; delsp=yes Content-Transfer-Encoding: 7bit X-Complaints-To: abuse@aioe.org User-Agent: Opera Mail/12.16 (Linux) X-Notice: Filtered by postfilter v. 0.8.2 Xref: csiph.com comp.lang.misc:2264 On Sat, 17 May 2014 17:17:19 -0400, James Harris wrote: > "Rod Pemberton" wrote in message > news:op.xf0siais6zenlw@localhost... ... > I was thinking more of *not* knowing or needing to know or even being > able to know all the details. One of the problems that Parnas' principles > were, I believe, designed to address was the ever increasing size of > software projects and the fact that problems being solved by computer > programs were becoming much larger than any one person could keep in his > head. Dividing the problem into smaller pieces helped to make large tasks > more manageable. Those who code Forth have a similar principle. They call it "factoring". Basically, they break the problem down into the absolute smallest testable unit. It's excellent for creating compact code, which was a noble goal in the 1970's and 80's on 8-bit home computers. The problem with factoring today is that it creates a huge amount of nesting of Forth words. The call overhead of all the nesting slows down both interpreted Forth and compiled Forth. It's somewhat like excessive use of procedures in C: /* untested */ int add(int a, int b) { return(a+b); } void store(int *a,int b) { *a=b; } int fetch(int *a) { return(*a); } void emit(char *s) { printf("%s\n",s); } ... store(&c,add(1,2)); /* c=1+2; */ if(fetch(&c)==3) emit("sum is 3"); ... Clearly, it's better to not have that extra overhead, if the code can be written directly. That means some tasks shouldn't always be divided into the smallest possible units. > Going back to that millons-of-lines program you mentioned, if the module > being changed was X and there were 100 other modules which used it then > rather than a programmer having to understand 101 modules he would have > only to know about 1 module and its interface contracts. Does that help > make sense of the principles? We had a situation where we had to change a structure used in our database. The amount of information we needed to record increased. Fortunately, the original authors provided a field at the start of the structure for a version number. Our options to fix the problem were: 1) change the version number, add code everywhere to check the version number and use the appropriate overlay (structure) 2) change the version number, rewrite the code to use a pointer to an overlay, set the pointer to the correct overlay based on the version number 3) read in all records in the old format, write out all records in the new format, and recompile the application to use the new format Which one did we choose? The answer to one of your questions is: "No idea ..." The answer to the other is #3. The record size, which was fixed, had to increase to support the larger records anyway. So, all records had to be read in and written out with more space, whether they were reformatted to the new layout or not. "Path of least resistance" comes to mind. >> Perhaps, it might be best to view this using the overlapping circles >> used in set theory? I.e., the intersection of the circles represents >> the efficiency or throughput or constraint or "richness" etc of the >> code interface. What you're saying to me is that it's best to restrain >> the size of X's circle (code base) allowing N's circle (code base) to >> get bigger, and/or to restrain the size of X and N intersection (limit >> the efficiency or throughput of the interface). > > No, I didn't mean that but I would try to > > 1. maximise the info X has about itself > 2. *minimise* the knowledge other modules have about X > 3. make as narrow as possible the interface between X and other modules. > There are numerous programming examples where increased complexity and an increase of the information flow between modules results in a more efficiency. That's why I have a problem with accepting #3. It may be the minimal result, but not necessarily the most efficient. So, if you're preferencing the minimal result, are you harming efficiency? > A good example is the memory allocators malloc, realloc, calloc and free. > They "know" a lot about each other in that they share data structures and > records. They offer to the rest of the world, however, a very small > interface and few insights into their internals. That's a good thing as > it isolates them from user programs and isolates user programs from them. > A system programmer could change various allocation internals such as the > data structures used to divide up memory or the OS call used in order to > request more. Or he could change from first-fit to next-fit or best-fit > etc. > None of those changes should prevent programs which call malloc and free > from functioning. If an applications programmer had looked at a > particular > implementation of malloc and found that it used a certain data > [structure] > and then written his application to depend on that then the app would be > vulnerable to changes in the malloc implementation. Would you agree that > keeping malloc's internals hidden is generally a good thing? In general, yes, I would agree, but that's only because I can't think of a decent counter example where someone would need access to malloc's internals. But, if I had some decent use for direct access to the information of malloc(), I'd have to disagree. But, I'd definately question the sanity of someone breaking apart a sort routine or search algorithm across multiple modules. I recall thinking a few years ago that some problem would only take a few lines of code to solve if I had access to the internal buffer of the function. I'm guessing this was strtok() ... So, where exactly should the boundary be drawn between information I'd like to use as a programmer and information the module provides to me? Who should draw the boundary? The user? He knows what he needs, but probably only when he needs it. The original coder? He only has expectations of what the users may need and clearly can't expect to know every intended use. So, that boundary could be too restrictive or too open, but I suspect "information hiding" will encourage too restrictive designs, at least in regards to efficiency. The concept of "information hiding" keep me thinking about what a coworker said about C++ being layers upon layers of unnecessary abstractions. > Implementations can change with little cost. But changing interfaces > can become very expensive. Well, that depends on the implementation. If they implemented version numbers as a part of the interface or as part of structs etc, then the code can work with changing structures and/or interface. > By the way, as you mentioned efficiency. Of course, people don't > usually rewrite code to make it slower. Um, let's not argue that one ... simpler code may be desired, a smaller memory footprint may be required, life has unintended consequences, experience and intellect affects results, people aren't actually replaceable in the workplace, and other such stuff. > More often code gets rewritten to improve its efficiency. But time > guarantees can be included as part of an interface if appropriate. Yes, but what if the efficiency can only improve by changing the "expensive" interface? Do you preserve the interface? Has doing so become "taboo" because of Parnas Principles? Rod Pemberton