Interfaces naturally emerge as software gets broken down into parts communicating with one another. The larger and more deliberate structures emerge from a deliberate attempt to organize the development process itself. [fn:Liskov2008] Structure often emerge directly from division of labor: as teams take on independent tasks, interfaces are established betweeen domains they become responsible for. (Conway’s Law)
Software developers are responsible for systems built out of very small atoms while ultimately performing tasks for their users of a much greater magnitude. Dijkstra showed this by computing the ratio between grains of time at the lowest and largest atoms of the system (from say, CPU instructions to a human interaction with the system) The span was already quite large by Dijkstra’s time, of about 10^9. Today this ratio would be at least above 10^12 (see grain ratios)
This large span has to be managed somehow, often through hierarchies of layers. [fn:EWD361]
[fn:Liskov2008] https://youtu.be/O6By99JW_V8?t=1647 On the desire to partition large systems into modules with intentional interfaces in order to manage the complexity of the software development at scale during the creation of the Venus operating system.
[fn:EWD361] https://www.cs.utexas.edu/~EWD/transcriptions/EWD03xx/EWD361.html
For besides the need of precision and explicitness, the programmer is faced with a problem of size that seems unique to the programmer profession. When dealing with “mastered complexity”, the idea of a hierarchy seems to be a key concept. But the notion of a hierarchy implies that what at one level is regarded as an unanalyzed unit, is regarded as a composite object at the next lower lever of greater detail, for which the appropriate grain (say, of time or space) is an order of magnitude smaller than the corresponding grain appropriate at the next higher level. As a result the number of levels that can meaningfully be distinguished in a hierarchical composition is kind of proportional to the logarithm of the ratio between the largest and the smallest grain. In programming, where the total computation may take an hour, while the smallest time grain is in the order of a microsecond, we have an environment in which this ratio can easily exceed 10^9 and I know of no other environment in which a single technology has to encompass so wide a span.
I am looking at practicionners not theoretical material.
https://simblob.blogspot.com/2019/10/verb-noun-vs-noun-verb.html?m=1
noun verb :: tends to discriminate more than, verb noun. This is interesting in that you want powerful verbs that apply to many nouns, however when you have a noun, you want to see a short list of verbs that apply to it. From specific to generic is better for useability than from generic to specific. See autocomplete.
> I really, really don’t like “anonymizing” APIs. They take your context-specific knowledge, throw it away, pack your tiny work item into a big generic pipe, then unpack on the other side with code that has to handle the worst case for each work item.
Example: a canvas drawing API that does not differentiate between transparent and non transparent fills means that the renderer implementer now has to differentiate between the two, because the two cases have very different performance profiles. Or something that turns a specific shape into a generic polygon.
http://www.randygaul.net/wp-content/uploads/2018/02/R.Gaul_APIs_ITCarlow.pdf
https://soundcloud.com/podcastcode/6-dont-make-me-write-ui
https://vimeo.com/10556923 at around 01:02 talking about interfaces
Cited as a good API by Casey Muratori
Cautionary tale about making interfaces internal to a system too rigid, which results in them not being improved. It is an angle where the context matters a lot. What is the audience of a piece and its interface? What communication delays are there?
On one end we have systems like the Linux kernel, which try to preserve external as well as internal interfaces stable. On the other end we have software whose internal structures shouldn’t be solidified too early.
Especially since some parts of the break-down that generates interfaces is largely accidental and a result of “problem solving by divide and conquer”
https://www.youtube.com/watch?v=RT46MpK39rQ&feature=youtu.be&t=27m51s
http://www.nrl.navy.mil/itd/chacs/sites/www.nrl.navy.mil.itd.chacs/files/pdfs/Heitmeyer2002.pdf http://web.stanford.edu/class/cs99r/readings/parnas1.pdf
http://www.pcg-random.org/posts/ease-of-use-without-loss-of-power.html http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2016/p0347r0.html
http://ozlabs.org/~rusty/index.cgi/tech/2008-04-01.html
https://anteru.net/2016/05/01/3249/
http://www4.in.tum.de/~blanchet/api-design.pdf
http://twitter.com/mbostock/status/681561150127878144
It’s funny how writing documentation can spur redesign: it’s easier to simplify a complex API than try to document it completely”
If you can’t come up with a good name for a method or a class, your design might be flawed
09-19-15 | Library Writing Realizations http://www.cbloom.com/rants.html
Hard to use those from the Efficient Programming with Components
Lecture 7 part 2
At that time Stepanov reveals that actual usage is necessary to write the generic operation:
https://youtu.be/S2iTfUyVOcY?list=PLHxtyCq_WDLXryyw91lahwdtpZsmo4BGD&t=273
Most of the times writing a program is an exploratory walk.
Lecture 11 Part 1, where he briefly mentions iterators and boost ranges:
https://youtu.be/84gHZgPCf1s?list=PLHxtyCq_WDLXryyw91lahwdtpZsmo4BGD&t=1455
Good choice of operator https://youtu.be/2mU8CTO2vSc?list=PLHxtyCq_WDLXryyw91lahwdtpZsmo4BGD&t=2922
And the reasoning behind advance and distance vs +=
https://youtu.be/_8hN232WNYU?list=PLHxtyCq_WDLXryyw91lahwdtpZsmo4BGD&t=276
You cannot know if you’re right until you found thing in context.
https://youtu.be/Dly8Ff4aDp8?list=PLHxtyCq_WDLXryyw91lahwdtpZsmo4BGD&t=1797
Write usage code first
The interface of rotate is designed to make it easy to compose. Its return value.
Other principle: don’t throw away information that your algorithm has already computed.
https://gist.github.com/pervognsen/d57cdc165e79a21637fe5a721375afba https://gist.github.com/vurtun/192cac1f1818417d7b4067d60e4fe921
- Write Usage Code First
- Don’t Throw Away Information that was computed
- Iterate
An algorithm should first be written in context i.e. the usage code is written first and the algorithm extracted from it.
It is even better if you have experimented with multiple usages so as to discover the best interface for the algorithm. Don’t worry however and be ready to revisit your API multiple times, as it is impossible to get right on the first try.
Anything that your algorithm is computing as part of its operation is of potential use by the user of this algorithm: don’t throw it away!
An example is a find function. It could return whether it found an element or not (bool) However in the process of finding that element it must have known the position of that element. So better return the position and not only a boolean. The position will be of great use for the user of your algorithm.
This is more about exploratory design than anything else.
Simon Cozens in a FOSDEM2015 talk about the SILE typesetting system:
https://youtu.be/5BIP_N9qQm4?t=1282
When you are implementign a subsystem, always implement it more than once. (…) that tells you where the separation of concerns lies.
One of the best piece of programming advice I ever got was from a guy I worked called Tony Bogen. He said that when implementing a subsystem always implement it more than once. And have two or three different variations of that because that tells you where the separation of concerns lies. And that’s been very helpful. Everytime I don’t do this I come to regret this because I generally end up writing that subsystem multiple times anyway.
Of particular interest its categorization of bad aspects, bad applications of concepts at the design stage, and its critique of existing software designs, including git.
Designing and Evaluating Reusable Components by Casey Muratori
See my “Commentary And Notes” notebook.
Reference: Granny Aninmation System 2.0
A common goal is to achieve code reuse.
Introduces the notion of Integration Discontinuity. This is basically what happens when the relationship between the user and the component being used starts growing to a point where the reused component does not provide what’s necessary for its user.
Theory and practice?
A component promises something but how is it in practice. Granny, first version built on reasonable code principles. Lots of people had problems integrating it. What happened?
The second incarnation was built after we learnt about it and this is our assesment
There are multiple tiers as you start integrating an API. They correspond to multiple stages of integration.
These tiers call for various characteristics. The idea though is that you let users of your API move between phases of integration so that they keep options open and don’t suffer from a discontinuity as they encounter a block.
https://vimeo.com/157022266 min 58:00 w/ Fabian Giesen
http://wiki.qt.io/API-Design-Principles
https://gist.github.com/uucidl/68d471b05c3a82d0f0556274f57cf6a3 http://ariya.ofilabs.com/2011/08/hall-of-api-shame-boolean-trap.html
tl;dr:
A good API is optimized for reading code not writing. One easy to spot API mistake is the “Boolean Trap.” It can be summarized with the following rule: “It is almost invariably a mistake to add a bool parameter to an existing function.
Also https://blog.ometer.com/2011/01/20/boolean-parameters-are-wrong/
TODO: enumerate the effects of adding a bool to an API entry point, depending on various properties of that entry point and the generated effect. (With respect to granularity/orthogonality/stability of interface etc..)
Omar Cornut (Dear Imgui) https://twitter.com/ocornut/status/888083327504097281
I’m now super careful with adding bool parameters to the public imgui api. Many bools became an api mess/issue in the long run.
So I have a public function and want to add a bool parameter to it. Other solutions seem overkill for now (Two entry points? Flags? Enum?)
https://www.youtube.com/watch?v=heh4OeB9A-c
@url: https://twitter.com/ArvidGerstmann/status/918757328920473600
<Avid Gerstmann> I said it before and I’ll say it again: ZeroMQ is the gold standard of API design. Super simple C API, yet incredibly powerful: http://api.zeromq.org/ The guide is a good read, although quite long: http://zguide.zeromq.org/page:all
Yes, it’s C, but it’s easy to built your own C++’ey abstraction on top of it, like you see fit. Many C++ APIs are too opinionated, or use STL. ZMQs C API is low-level & flexible. I dislike having a high-level API & prefer building it myself. ZeroMQ also gets this right. They offer CZMQ, a separate high-level library, wrapping the low-level library & providing a nice API.
http://research.microsoft.com/en-us/um/people/blampson/70-SoftwareComponents/70-SoftwareComponents.htm an answer by stepanov: http://www.stepanovpapers.com/Industrializing%20Software%20Development.ppt https://softwareengineering.stackexchange.com/questions/221615/why-do-dynamic-languages-make-it-more-difficult-to-maintain-large-codebases/221658#221658
on that topic: http://www.uh.edu/engines/epi1252.htm http://st.inf.tu-dresden.de/files/teaching/ss10/cbse/01-introduction.pdf http://www.cl.cam.ac.uk/~srk31/research/talks/kell16operating-slides.pdf
https://www.microsoft.com/en-us/research/publication/hints-for-computer-system-design/ https://www.microsoft.com/en-us/research/wp-content/uploads/2016/02/acrobat-17.pdf
Parnas D.L. On The Criteria To Be Used in Decomposing Systems Into Modules, Comm. acm 15 12, dec 1972 p 1053-1058
http://repository.cmu.edu/cgi/viewcontent.cgi?article=2979&context=compsci https://www.cs.umd.edu/class/spring2003/cmsc838p/Design/criteria.pdf
Britton, K..H, et al. A procedure for designing abstract interfaces for device interface modules. Proc. 5th Int’l Conf. Software Engineering, ieee Computer Society order no. 332, 1981, pp 195-204.
“The people who rely on the compat layers don’t care enough to maintain it. The people who work on the mainline system don’t care about the compat layers because they don’t use them. The cultures aren’t aligned in the same direction. Compat layers rot very quickly . ” – Theo De Raadt
aka the REST API paper
“The strong typing of object-oriented languages encourages narrowly defined packages that are hard to reuse. Each package requires objects of a specific type; if two packages are to work together, conversion code must be written to translate between the types required by the packages.” [ John K. Ousterhout]
Also notes about reuse’s failure.
http://loup-vaillant.fr/articles/implemented-my-own-crypto
http://staltz.com/api-design-tips-for-libraries.html
http://timperrett.com/2016/11/12/frameworks-are-fundimentally-broken/
https://www.sebastiansylvan.com/post/matrix_naming_convention/
Reading https://twitter.com/antirez/status/958028135605383169
See Rax api at: https://github.com/antirez/rax
After delivering a shitty API like Redis’s dict.c hash table, this time I did my homework well and the rax.c (radix tree) API is really pleasant to use.
Dict API:
#if 0
// Macros:
dictFreeVal(d, entry)
dictSetVal(d, entry, _val_)
dictSetSignedIntegerVal(entry, _val_)
dictSetUnsignedIntegerVal(entry, _val_)
dictSetDoubleVal(entry, _val_)
dictFreeKey(d, entry)
dictSetKey(d, entry, _key_)
dictCompareKeys(d, key1, key2)
dictHashKey(d, key)
dictGetKey(he)
dictGetVal(he)
dictGetSignedIntegerVal(he)
dictGetUnsignedIntegerVal(he)
dictGetDoubleVal(he)
dictSlots(d)
dictSize(d)
bool dictIsRehashing(d)
#endif
dict *dictCreate(dictType *type, void *privDataPtr);
int dictExpand(dict *d, unsigned long size);
int dictAdd(dict *d, void *key, void *val);
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing);
dictEntry *dictAddOrFind(dict *d, void *key);
int dictReplace(dict *d, void *key, void *val);
int dictDelete(dict *d, const void *key);
dictEntry *dictUnlink(dict *ht, const void *key);
void dictFreeUnlinkedEntry(dict *d, dictEntry *he);
void dictRelease(dict *d);
dictEntry * dictFind(dict *d, const void *key);
void *dictFetchValue(dict *d, const void *key);
int dictResize(dict *d);
dictIterator *dictGetIterator(dict *d);
dictIterator *dictGetSafeIterator(dict *d);
dictEntry *dictNext(dictIterator *iter);
void dictReleaseIterator(dictIterator *iter);
dictEntry *dictGetRandomKey(dict *d);
unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count);
void dictGetStats(char *buf, size_t bufsize, dict *d);
uint64_t dictGenHashFunction(const void *key, int len);
uint64_t dictGenCaseHashFunction(const unsigned char *buf, int len);
void dictEmpty(dict *d, void(callback)(void*));
void dictEnableResize(void);
void dictDisableResize(void);
int dictRehash(dict *d, int n);
int dictRehashMilliseconds(dict *d, int ms);
void dictSetHashFunctionSeed(uint8_t *seed);
uint8_t *dictGetHashFunctionSeed(void);
unsigned long dictScan(dict *d, unsigned long v, dictScanFunction *fn, dictScanBucketFunction *bucketfn, void *privdata);
uint64_t dictGetHash(dict *d, const void *key);
dictEntry **dictFindEntryRefByPtrAndHash(dict *d, const void *oldptr, uint64_t hash);
Rax API
/* A special pointer returned for not found items. */
extern void *raxNotFound;
rax *raxNew(void);
int raxInsert(rax *rax, unsigned char *s, size_t len, void *data, void **old);
int raxRemove(rax *rax, unsigned char *s, size_t len, void **old);
void *raxFind(rax *rax, unsigned char *s, size_t len);
void raxFree(rax *rax);
void raxFreeWithCallback(rax *rax, void (*free_callback)(void*));
void raxStart(raxIterator *it, rax *rt);
int raxSeek(raxIterator *it, const char *op, unsigned char *ele, size_t len);
int raxNext(raxIterator *it);
int raxPrev(raxIterator *it);
int raxRandomWalk(raxIterator *it, size_t steps);
int raxCompare(raxIterator *iter, const char *op, unsigned char *key, size_t key_len);
void raxStop(raxIterator *it);
int raxEOF(raxIterator *it);
void raxShow(rax *rax);
uint64_t raxSize(rax *rax);
One aspect that makes it good, is that we can see this API apply directly to a hash table like dict almost out of the box. As long as the user is willing to convert their key types to bytes. Some may consider writing these bytes as too expensive, however I’d argue it’s easier to get bad performance out of injecting equality and hashing callbacks into a hash table. The hashtable has to be able to compare for equality, afterall.
@url: https://flutter.io/design-principles/#introduction @title: Flutter Design Principles
Breaking API changes are treated as specific items worthy of attention. Proposals are written and summarized.
- Justification is made
- A migration path from old to new code is described
- Contact for supporting people to move old to new API
Weighting of API stability versus benefits will determine whether the breaking change is to be made.
Transition period by way of annotating old code as deprecated is to be introduced. (using tags such as @escape{@deprecated(‘Description’)})
- Avoid hard to maintain data-retention/duplication
@quote{There should be no objects that represent live state that reflects some other state, since they are expensive to maintain. e.g. no HTMLCollection}
I.e. they discourage data-retention, as it requires synchronization between layers. HTMLCollection
- Easy access implies cheap access
If something is implemented via what looks syntactically cheap, it should also be cheap.
- Expensive operations should not be exposed as synchronous procedure calls
- APIs should be arranged in “physical” levels.
- Convenience APIs layered on top of lower-level APIs
- Scope of each level is made as narrow as possible
- Unsafe constructs are not promoted as regular APIs
ex: low-level construction of executable code from unsafe/user input pieces
- Adapter APIs should be complete
When wrapping another API, faithfully wrap the complete API so as to minimize surprises and avoid creating integration surprises.
@url: https://code.kiwi.com/code-design-principles-for-public-apis-of-modules-6a43aaf26624 @title: Code Design Principles for Public APIs of Modules
Main actions on a module:
- Creation from scratch
- Redesign
Heuristics
Names
Remove anything that appears unnecessary (information compression) .. without going as far as hurting redability.
Example of a name that can be shortened, kw.booking.additional_booking_management could be better written kw.booking.additional_booking, as “management” does not appear to contribute much meaning.
Goals for better names:
- legible useage code, emphasizing its own logic (names that are too long might disrupt legibility of the useage code)
- names that are easy to be kept consistent (i.e. don’t invite variations)
Insights for better APIs
- how many engineers would maintain that module
- how many engineers would commonly write code using that module
- how frequently code using this API might be read over time (times/day)
- how long might this module be used
- consider what the user will see
This should help you decide the level of quality this API and implementation should be at.
Before making an implementation clean, think about the user first, and make the API clean. Write tests, prototype useage code.
Procedures
- keep number of positional parameters small (< 3)
- procedures are designed to work on data (if the procedure of a verb, its object), so design/specify that
<@pkhuong> Oct 9 Reading Fluent Python… does the official Python documentation really recommend hashing objects by combining their fields’ hashes with xor?
<@pervognsen> Oct 9 Huh? I’ve always just hashed a tuple of the fields…
I’ve definitely seen recommendations like that in various Java books, sadly, but Python makes it so easy to just hash a tuple.
Incidentally, one of my favorite general tricks is to exploit tuple isomorphisms for boilerplate code like that.
<@pkhuong> Oct 9 For sure. Much easier than writing our all 6 comparator methods by hand (:
I assume you also hash in the class name :p
I’ve always thought that hash methods should return an opaque hash_t type, so you aren’t even tempted to do this kind of crap.
<@pervognsen> Oct 9 I think the right interface for extensible hashing is closer to serialising data to a stream (that happens to hash its input bytes). I saw a C++ standard proposal like that; I don’t know what happened to it.
<@pervognsen> Oct 9 Yeah, I was about to correct myself. You need that for streaming. Then you just provide variadic helpers for the combiners. And it lets you distinguish internal hash state vs final hash value.
I prefer the “fold/append/mix into hash state” API for a systems programming language since it doesn’t involve other intermediate objects.
Hrm, I vaguely remember the C++ proposal you’re referring to. I don’t think it was ratified, but I recall it used the streaming style.
<@matt_dz> Oct 9 A couple, most recent in 2015 (http://wg21.link/P0029 ) and 2016 (PDF: http://wg21.link/P0199 ); latter withdrawn: https://botondballo.wordpress.com/2016/07/06/trip-report-c-standards-meeting-in-oulu-june-2016/ ….
@url: https://twitter.com/ocornut/status/941627778910380032
<Omar Cornut> dear imgui: figuring out how to interface with Windows drag and drop, and it turns out that when using WM_DROPFILES it is trivial but super limited, and anything else seems like pain.
<Leonard Ritter> the windows drag & drop API seems really weird until you implement your own and discover all these corner cases and go “oh. okay. now i get it”. the GTK one is just as crazy. For my own imgui-based editor I built a drag & drop system that was purely internal, and that alone got fairly complex already. it’s like a network protocol that goes “i have these formats to drop, which one is fine?” – “these are fine” – “can i drop this one?” – “you may” And because, for the desktop context, it’s also IPC, you need mimetypes or some other global enumerator so y’all understand each other.
<Leonard Ritter> In an earlier windows-based UI for an audio app i ended up using the d&d system for all kinds of stuff (including a dsp node editor) because it was so versatile.
<Omar Cornut> What’s annoying is there are so few good examples of how to support Win32 DND in your app, and once you get past WM_DROPFILES you have to do the whole thing. I was wondering if we can just somehow more easily implement a IDropTarget to get a better WM_DROPFILES with preview.
<Omar Cornut> There’s a beta drag and drop api in imgui master if you want to check it out for suggestions (its basically 6 functions). It’s missing demos but there are a few uses of it in the code.
<Omar Cornut> Drag target can compare data format (which are essentially strings in dear imgui land). The main thing that missing now are A) the possibility to transport multiple data formats simultaneously B) the possibility for the source to compute the payload later (e.g. on drop)
https://twitter.com/EricLengyel/status/938590610604175360
<Eric Lengyel> Slug was designed to have no dependencies whatsoever. It doesn’t make calls into anything external to itself, including the standard library, any rendering API, and the operating system. It doesn’t even allocate memory.
<Gustavo Samour> The API gives you the buffer size, you allocate, and then pass in the pointers back to the API
<Eric Lengyel> Same goes for vertex buffers. The CountSlug() function tells you how much space you need to allocate for the BuildSlug() function to fill with vertex and triangle data. (Slug does not use callback functions to perform allocations.)
<Dale Kim> I feel that this style of API needs to have more visibility. A lot of folks see a library as code that does something for you, but many libraries do this by also taking away some of your control. Memory being owned by the library is a common example.
Whether or not the API controls you or you control the API is a central issue I see in many problematic systems. In most cases, you want the caller to retain as much control as possible.
<Tom Forsith> It’s OK to ship with defaults to do those things. Granny is the same - if you do nothing, it uses a bunch of standard OS calls. But you can override them easily, either by callback or pre-emption.
<Bryan McNett> even then, i like my libraries to treat allocations as special events, only to be done predictably and rarely.
<Tom Forsith> Agreed. “Create” should be in the name of the function, etc.
- MIDI
- x86 ISA
- SQL
- OpenGL
- Any programming language
These are examples of well-defined interfaces which have allowed big components reuse and their independent improvement/optimization.
For example, a programming language is a well defined interface, to reusable components (compilers) creating computing automatons (programs)
Useability is how well (speed, effort) an user can achieve a goal, within a certain context.
Ex: “Given that I don’t know what to find, and want to search the internet, google’s search, with its single text entry and quick results gives me an effortless and quick list of potentially interesting web links”
Bad | Good | |
---|---|---|
Opaque data | Transparent data | Efficient Basis |
Category | Good | Bad |
Redundancy | Offers smooth gradiant for integration | |
Redundancy | Accomodates user types | |
Redundancy | Noisy, hard to grasp | |
Low Granularity | Flexibility | Simplicity |
Coupling | Inflexible | |
Coupling | Defects easy to create |
Relationship between idempotence and declarativeness. I.e. you can apply f as many times as you want because it encodes something about the end result, not an action to be perform. For pure functions it is easy, for procedures the trade-off is in some form of state retention.
Tadashi Takieda says:
count(Def) < count(Theorems) < count(Examples)
count(Def) < count(LogicalResults) < count(Examples)
https://www.youtube.com/watch?v=J7vojBbvudQ&feature=youtu.be&t=139
“Design Of Everyday Things” ; Affordances ; Design of clean programming interfaces: tools coming from Design, tools coming from Mathematics ; Mathematics is mostly about user interface ; Theories and concepts as user-interfaces
Industrial design has traditionally seen itself as a way to attract and seduce a customer (see “Never Leave Well Enough Alone” by Raymond Loewy) Some amount of surprising arrangements, within the constraints of serving logically the function of an object was therefore necessary. It seems mostly useless and slightly harmful to care about this aspect for internal modules. It could be a factor of success for an opensource or commercial library.
In the book see his example of the egg as an ideal form perfectly adapted to its function. Balance between strength and aerodynamics. The principle of economy (of materials for instance) leads to elegance.
A first principle of construction: on no account allow the engineering to dictate the building’s form . . . . never modify the social spaces to conform to the engineering structure of the building. –Christopher Alexander
“Abstraction”
https://twitter.com/tom_forsyth/status/924692979721281537
My rule is - when writing at layer X, if you can’t easily say what the layer below looks like, you’ve abstracted too far.
Traditional software engineering wisdom says
wisdom | counterpoint |
---|---|
Target an interface, not an implementation | Hard to do if you don’t have two implementations |
Interfaces should hide change-prone details | Planning fallacy, risk of hiding interesting problem domain details |
Don’t Repeat Yourself | Creates central points of failure and bottlenecks |
- Retained/Immediate (Q. about data-retention / automation)
- Push/Pull (Q. about latency / single vs multiple control flows)
https://www.youtube.com/watch?v=oyLBGkS5ICk
There are two types of changes to an interface: grow or break. At every level of what an API provides and requires:
Grow: provide more, require less Break: provide less, require more
Levels:
- artifacts
- names
- functions
https://lwn.net/Articles/336262/
http://www.open-std.org/jtc1/sc22/wg14/www/docs/n1967.htm
Darryl Havens (responsible for the I/O system on NT)
“If you spend that amount of time designing something and you have a spec that gives you every single API [application programming interface], what its inputs are, what its outputs are, you pretty much know how the thing is going to work,” Havens said. “So I actually typed in the code for the entire I/O system in three weeks. That’s how well-designed it was. By the time I sat down to write the code, I already pretty much knew how it was going to work.” Read more at https://news.microsoft.com/features/the-engineers-engineer-computer-industry-luminaries-salute-dave-cutlers-five-decade-long-quest-for-quality/#ydU2bC61j8j51kq6.99
++number_of_people_who_reported_the_documented_stb_image_behavior_as_a_bug; (It’s the “channels_in_file” output parameter. The #1 API regret in all of the stb libraries, but fixing it would break backwards compat, so @nothings and me have shied away from it.)
We’re thinking about a major release at some point that groups a bunch of API changes (including removing some of the more obscure formats, getting rid of the globals for error reporting, and cleaning up the currently messy 8-bit/16-bit/float situation), but this is the #1 thing.
every API designer has some regrets, but it’s darkly funny even stb_image, with a primary API consisting of one entry point, has That One Thing.
He names “patterns of control” which can be useful to characterize an API.
In the sequential pattern of control, the caller hands execution fully over to the module. In the incremental pattern of control, the module and caller alternatively take the execution control. In the parallel pattern of control, the work of the module is concurrent with its caller.
https://www.amazon.com/API-Design-C-Martin-Reddy/dp/0123850037