From mboxrd@z Thu Jan 1 00:00:00 1970 X-Spam-Checker-Version: SpamAssassin 3.4.4 (2020-01-24) on polar.synack.me X-Spam-Level: X-Spam-Status: No, score=-1.9 required=5.0 tests=BAYES_00 autolearn=ham autolearn_force=no version=3.4.4 X-Google-Thread: 103376,699cc914522aa7c4 X-Google-Attributes: gid103376,public X-Google-Language: ENGLISH,ASCII-7-bit Path: g2news2.google.com!news4.google.com!news3.google.com!newshub.sdsu.edu!nntpserver.com!zeus.nntpserver.com!newsfeed.arcor.de!newsspool1.arcor-online.net!news.arcor.de.POSTED!not-for-mail Newsgroups: comp.lang.ada Subject: Re: Structured exception information From: Georg Bauhaus In-Reply-To: References: <1169819196.5976.57.camel@localhost.localdomain> Content-Type: text/plain Content-Transfer-Encoding: 7bit Organization: # Message-Id: <1170010221.6748.194.camel@localhost.localdomain> Mime-Version: 1.0 X-Mailer: Evolution 2.6.1 Date: Sun, 28 Jan 2007 19:50:21 +0100 NNTP-Posting-Date: 28 Jan 2007 19:50:15 CET NNTP-Posting-Host: f72a07e3.newsspool3.arcor-online.net X-Trace: DXC=M6?2Abg`Zfk016@cHD@m;jMcF=Q^Z^V3h4Fo<]lROoRagUcjd<3m<;baRI?S[K>i7dPCY\c7>ejVh8@T?L8CjN9hI;X51ZTd5Ig X-Complaints-To: usenet-abuse@arcor.de Xref: g2news2.google.com comp.lang.ada:8650 Date: 2007-01-28T19:50:15+01:00 List-Id: On Sat, 2007-01-27 at 13:17 -0500, Stephen Leake wrote: > In addition, you have ignored the problem that each version of > Write_Output for different hardware will need need a corresponding > version of the exception type; more work. Versions of Write_Output for different hardware might also need a different string passed with the exception? For example in order to clearly indicate which hardware is reporting. I'm not so sure a different type is needed for every variation in hardware. A serial number component might do, if the hardware is sufficiently similar. It _is_ more work to write good state/error reporting modules. But there are some things in Ada that support "message management" quite well, from a project point of view I think. One of these is the case coverage rules. (Exception types would make this even more integrated.) If a message has an ID of some enumeration subtype, it is unique per program or library. Once you have the message types, starting e.g. with type Diagnosis is (Hardware, Parameters, Connection, ...); you can construct aggregates of messages. A simple case is when the messages are indeed string literals, possibly in different languages. (This setup can be extended to include typed information from the exception origin, formatting, and more.) type List_Of_Messages is array(Diagnosis) of String_Ptr; type I18N_Messages(language: ISO_639) is record texts: List_of_Message; end record; Japanese_Messages: constant I18N_Messages(Language => JP) := (texts => (Hardware => new String'("..."), Parameters => new String'("..."), ...); You can now easily construct translations that are type checked: Write a simple program that shapes the strings of a List_of_Messages for Qt linguist XML, or Excel, or GNU gettext etc.. (IIRC, http://www.mckae.com/xia.html has a module for transforming Ada objects to XML.) Have someone translate the messages using for example the Qt linguist tool, output will be XML again. Then use a simple XSL program to create another Ada declaration from the XML, for the tranlated messages. (You won't need to link Qt to your program; you just use the very comfortable tool - or your favorite XML editor, and then run the XSL program. Or write the message translations using Ada directly.) The Ada compiler checks coverage. The translations should therefore have one string for each value of the Diagnosis type. For this procedure to work, you won't even need most of your program because the strings are collected in one place. They are not the result of scanning the entire program for possible message strings. If you use the linguist program, this points to a possible way of including additional information like the device name or errno using %1, %2, ... message forms similar to sprintf format strings, and similar but not equal to GNU gettext: Suppose there is an exception type hierarchy. type Device_Error(What_Happened: Diagnosis) is new Predefined_Exception_Type with record Device_Name: String(1 .. 12); errno: C.int; end record; Then, The_Messages: constant List_of_Messages_Ptr := Japanese_Messages'access; For a command line tool, procedure Show_Error(Issue: Device_Error'class) is Text: String_Ptr renames The_Messages.texts(Issue.What_Happened); -- say, "can't open device %1 : %2" begin Fill_In(Text, Issue); -- %1 is filled using Issue.Device_Name -- %2 is filled using Issue.errno Output_Device.Show(Text); end Show_Error; or, ignoring the text messages entirely, procedure Show_Error(Issue: Device_Error'class) is Alarm: Sound; begin Interpret(Issue, Result => Alarm); Midi.Play(Alarm); end Show_Error; That is, messages are not frozen where the exception is raised, but where they are displayed, played, read, where they turn the red lights on, and the sirens too etc.. > Hmm. With the choice of names you used in Set_Error, you seem to be > implying that there is a "general error description language", that > Set_Error can enforce. I don't think that's true. I think every application has a few classes of errors. For every class, have a small tree of parameterized exception information types, as outlined above. > I guess by "validation" you mean the check "user_volts > Max_volts", > and the Check_* procedures I quoted above. Yes. > > when the *message* should change even though the validation didn't > > change. From this viewpoint Write_Output mixes two separate > > concerns: input checking and UI message construction. > > People keep saying those should be separate concerns, but I don't > accept that. Why is it _better_ that they be separate? Depending on the type of program, using MVC and corresponding typed objects might be too complex, unnecessary work, etc. But once you have a package of reporting routines, it's not that much work. Typed exception information will help in creating variants of these. > Formatting a string is the best > way to do that. Right, a string that explains the issue to the user. But the string need not be produced by the program, as I've shown in another post about LEDs with a legend. Why is it better to have this setup that seems more complicated? (Isn't it strange to hear an Ada programmer ask why it is better to not just lump information into a string? :-) :-) Now who best writes the messages (1), what should be included (2), and where in the program should they be formatted(3)? (1) Programmers tend to leave out necessary context information judging by the brief shouts of command line tools that have caused so much anger and Google searches for explanations. Natural language experts aren't usually available during development, so it seems an advantage to add another level of indirection, just provide the information, and delegate the formulation of messages to the domain experts. (2) How can a programmer anticipate how the information "raised" from his/her module is best used elsewhere? E.g., MS recommends not to show stack traces from failing ASPx programs to end users. Perhaps show a nice page with some error code and an invitation to report or come back later. But what can an Ada exception handler do here if all exception information is already frozen inside a string? Parse it? (3) As you have explained, SPO sentence construction rules are not universal. The usual approach is therefore to use sprintf- like procedure calls for message construction. Yet, whatever the grammar rules of a natural language might require, the information available *for* message construction is unchanged by the fact that it can be turned into a sentence. So why not create or extend a type for the information and pass objects of the type, not formatted strings? A related example. I find it quite helpful to see how two different compilers analyze my programs, hence how different groups of people create messages. When one of the compilers says, foo.ada: ... P(x) is not appropriate The other might say foo.ada: ... P(x) can only be used if Q(x) Granted these are messages from different compilers and not really exception messages. Still, chances are that some Q*(x) is available to the first compiler, too, because it reports an error for the same input, without informing about Q*(x). It's just a Not. This shows the importance of messages: Good messages can create huge savings, by reducing the time consuming and frustrating work of finding out *why* P(x) is not appropriate in the first error message. Avoids anger, too. Suppose now that you had a compiler where you could augment error messages yourself, without touching the compiler. Example: foo.adb:18: invalid prefix in selected component "FOO" where FOO does _not_ denote the component. Suppose further that this error message had a unique ID, e.g. i_p_s_c_2. The compiler user can now associate hints with the message by writing a corresponding statement such as for Message_Hints use I_P_S_C_2 := "The identifier shown could be the prefix"; end; In summary, then, I would prefer the separation of typed exception data and exception message strings. The first is far more flexible and accessible for anyone but the lazy programmer who writes the exception message strings after "with" ;-) > > or a Spanish character set. > > I think that is a valid objection; other parts of this thread have > talked around it. My application has no (current :) requirement for > this, so I can ignore it. That's common practice I guess. For many programs I wouldn't waste time on more sophisticated reporting, too. But this luxury fails once the assumptions change, or the requirements ask for more :-)