Prev: Deriving from a class derived from a CDialog
Next: How to show underlined menu shortcut letters by default for a contextmenu ?
From: Hector Santos on 24 Jun 2010 08:57 Goran wrote: > On Jun 24, 1:18 pm, Hector Santos <sant9...(a)nospam.gmail.com> wrote: >> But again, you proved my point. Until you are completely aware of all >> the possible specific exceptions traps for a class, a documentation >> and learning issue, the "catch all" is going to be common place. > > Hey, I am trying to grind a different axe here! > > I am trying to say this: exceptions or error-return, it's both > unrealistic and needles to be aware of / handle all possible > exceptions / failure modes at any given place. It's easy to see why > it's unrealistic: consider your own function somewhere in the call > stack. Say that it can call X "base"/"system" functions, each having a > small number of Y failure modes, some of them overlapping, for a total > of e.g. X*Y*0.75 failure modes. Say that function is in usual win32/ > CRT form of "BOOL result + GetLastError" How do you work with that? > > if (!myfunc(params)) > switch (GetLastError()) { bloody hell, it's ful of cases! } > > ? This is why I said, as with the simple fopen(), the most typical errors of "interest" are literally related to: not found bad input locking sharing any other error is more system or hardware related. > Of course not. For the most part, you just do > > if (!myfunc(params)) > return false; > > (or equivalent thereof) Could be, sure. For me, for example, logging functions: FILE *fv = fopen(filename, "at"); if (fv) { fprintf(fv,whatever); fclose(fv); } but there are cases where in my more elaborate class based logging class, where there is rotating of files, etc, there are some error checking, including a general catch all for the valist. > Typical error-return code looks like the above. IOW, for the most part > (like, 90% of cases), it does not try to "handle" any failure modes at > the place of the call. Not quite sure if I follow, and if I did, I would not be among the 90%. I generally focus contain errors at the point of functionality. I really use a catch all, this is more "recent" for me and only where required, i.e, ODBC classes. I make extensive use of exceptions for our RTE for our Wildcat! BASIC p-code language, where like a .NET environment, you must capture programmer faults so it won't bring the Wildcat! Interactive NET server "OS" per se. Even in our WCBASIC language were we offer CATCH exception trapping, the same issues apply, - do you use specific catch traps or catch alls. It depends. For example, since our system deals with communications, WCX (compiled wcBASIC applets) has "Connection Drops" concepts. If the programmer did not add any CATCH blocks, then the RTE will abort the WCX at the next immediate OP code, cleaning up all its managed resources. But the programmer who needs graceful trapping or detection of connection drop can do so as well, and for a CATCH, the RTE will jump to that block, etc. But the programmer can also disable the RTE connection drop detection and allow the applet do the detection or not at all. The latter might be useful where it knows it will complete and needs no interruption regardless of a connection drop, or it can do a check and gracefully clean up and exit. etc, etc, etc. > Well... (drum roll...) exceptions give you that behavior automatically > (and number of failure modes does not change due to them; in that > respect, there's no difference). But you seem to want to catch all > these exceptions. Now I clearly have to ask: why? Just do "return > false"! (That is, in case of exceptions, do nothing). That is the > error of your ways WRT exceptions. No, seriously! Again, it was more of a generalization. As I learn .NET, I will trap catches around usage of .NET library classes, etc, because I am not fully aware of all the possible issues with it. Example, where I started with a global and then fine tune it. For wcLex, the simple "get going" usage of a socket listener: try { server = new TcpListener(localAddr, port); server.Start(); worker.ReportProgress(0, "- Waiting for clients"); uint nClientTid = 0; while (true) { nClientTid++; ClientData ctcp = new ClientData( server.AcceptTcpClient().Client, nClientTid); worker.ReportProgress(1, ctcp); } } catch (SocketException err) { // 10004 expected for breaking socket handle if (err.ErrorCode != 10004) { worker.ReportProgress(0, String.Format("! SocketException: ErrorCode: {0}", err.ErrorCode)); } } finally { // Stop listening for new clients. worker.ReportProgress(0, "- Ending"); server.Stop(); } The above was quick original code, and then the fine tuned to handled to bunch of scenarios, some which only came after KNOWING what part of the socket class does what. For example, when it came to testing conflictive bindings (two instances of wcLEX trying to open the same address and port), the CATCH ALL showed the socket exception. The question was, which call throw it? Was it? server = new TcpListener(localAddr, port); or was it? server.Start(); It was the latter, so a try was added around it to catch that specific error event. Since the above was part of the BackgroundWorker_DoWork event, I also spend time to send feedback to the calling RunWorkerAsync() to above the starting of the server. Throwing exceptions in dowork did not get caught. I need to learn about that aspect of it. But I used mutexes to signal a StartupOK event. Here is the code, unchanged: private void backgroundWorkerServer_DoWork(object sender, DoWorkEventArgs e) { BackgroundWorker worker = sender as BackgroundWorker; try { worker.ReportProgress(0, "* Starting NNTP Server"); server = new TcpListener(localAddr, port); server.Start(); StartupOk = true; StartupEvent.Set(); } catch (SocketException err) { if (err.SocketErrorCode == SocketError.AddressAlreadyInUse) { worker.ReportProgress(0, String.Format("! Socket Address/Port already in use: {0}:{1}",mySettings.IPAddress, mySettings.PortNumber)); } else { worker.ReportProgress(0, String.Format("! Init SocketException: ErrorCode: {0} {1}", err.ErrorCode, err.SocketErrorCode)); } StartupEvent.Set(); return; } try { worker.ReportProgress(0, "- Waiting for clients"); uint nClientTid = 0; while (true) { nClientTid++; ClientData ctcp = new ClientData(server.AcceptTcpClient().Client, nClientTid, mySettings); worker.ReportProgress(1, ctcp); } } catch (SocketException err) { if (err.ErrorCode != 10004) { worker.ReportProgress(0, String.Format("! SocketException: ErrorCode: {0}", err.ErrorCode)); } } finally { // Stop listening for new clients. worker.ReportProgress(0, "- Ending"); ShutDownEvent.Set(); server.Stop(); } } Note, the ShutDownEvent mutex was used to synchronize an EXE shutdown and the logging of the status becoming invalid when the application thread is exited and thats because I switched to using in Program.cs Form1 form1 = new Form1(); Application.Run(); GC.KeepAlive(form1); to control the flickering of the always startup visible window made hidden when the notifyicon is active. So the point is, that even when you do know how the exceptions and flow should be done, it can CHANGE depending on other factors. See the thread where I talked about the LiveID SDK example Live ID DLL binding with the EXE no longer applies when the DLL is bound to a helper DLL. There the exception logic must change. > >> Maybe Intellisense should have a macro for extracting from a selected >> class and wrapping try block adding all the possible specific >> exception catch blocks. > > Seems like you yearn for Java checked exceptions ;-). What IDE do you use? -- HLS
From: Joseph M. Newcomer on 24 Jun 2010 09:39 It's called a "database" and I'm sure that databases (which require the ability to append new records) are not in the 5% category... joe On Wed, 23 Jun 2010 16:34:02 -0700, "David Ching" <dc(a)remove-this.dcsoft.com> wrote: >"Joseph M. Newcomer" <newcomer(a)flounder.com> wrote in message >news:8445269je2c1ekr6ukuj6g0e5gcj9iv2ar(a)4ax.com... >> while >> you are writing, anyone else should be free to write in non-append mode. >> So if I am >> adding record 703, there is no reason to prevent anyone from (re)writing >> records 0..702. >> That's why file locks exist. And they can lock the range of record 703. > >Interesting, thanks. I hadn't thought of allowing other processes to >rewrite existing data while only you could append new data. That seems like >it would be rarely used, perhaps that is the comment you got about only 5% >of the people needed to do that? > >-- David Joseph M. Newcomer [MVP] email: newcomer(a)flounder.com Web: http://www.flounder.com MVP Tips: http://www.flounder.com/mvp_tips.htm
From: Goran on 24 Jun 2010 09:54 On Jun 24, 2:57 pm, Hector Santos <sant9...(a)nospam.gmail.com> wrote: > > Of course not. For the most part, you just do > > > if (!myfunc(params)) > > return false; > > > (or equivalent thereof) > > Could be, sure. For me, for example, logging functions: > > FILE *fv = fopen(filename, "at"); > if (fv) { > fprintf(fv,whatever); > fclose(fv); > } Yes, this is a (well known? perhaps) issue when going exceptions. You need to turn things on their heads somewhat. Supposing that you throw, whatever happens, somewhere down the line you have to report this error. The best way to do this is to have nice "original" error info. In this case, that's relatively easy, because you have file name and errno - all is there. So for logging, there's, again, nothing to do if you use exceptions: error will propagate to somewhere up the stack, and.. Surely you won't forget to log it. But! But... It's __essential__ not to lose sight of the original error and to be comprehensive with error info. Now, supposing that you have several places where you might get into a particular situation, and after seeing the error in your log, you don't know where that place is (if you will, you know the line, but it's important t know where did you come from to it). Then, you have two possibilities: 1. have "debug" logging, turn it on and look there to see where things start to fall apart. Combine with error log and there you are. 2. "enhance" original error while unwinding the stack (add "context" if you will). At any rate, I am pretty certain that exceptions won't really stop you from getting the desired result. But one has too stop with error- return thinking first (don't be mad at me, but I believe you can't let that go). Instead, you have to think this way: any given line can throw, bar those specifically crafted not to (and they are rare and hence +/- easy to spot). Next, you start thinking in terms of exception-safety guarantees for any code snippet/function. For example, any resource allocation might need "protection". > > but there are cases where in my more elaborate class based logging > class, where there is rotating of files, etc, there are some error > checking, including a general catch all for the valist. > > > Typical error-return code looks like the above. IOW, for the most part > > (like, 90% of cases), it does not try to "handle" any failure modes at > > the place of the call. > > Not quite sure if I follow, and if I did, I would not be among the > 90%. I really have to press you here: so what code do you have after a failed call? Provide some examples, and I am pretty confident that, even if you used exceptions, you could get what you need pretty easily (that is, even if you "handle" errors, it's trivial to do what you do either way, exceptions or error-return). Here's my bet: most of the time, you have error cleanup, for which you should use RAII in e.g. C+ +, or "using" in C#, or you have logging (see above about that). In rare places, you have "corrective" action. But I bet you that these are __rare__. Goran.
From: Hector Santos on 24 Jun 2010 11:14 Goran wrote: > Now, supposing that you have several places where you might get into a > particular situation, and after seeing the error in your log, you > don't know where that place is (if you will, you know the line, but > it's important t know where did you come from to it). Then, you have > two possibilities: > > 1. have "debug" logging, turn it on and look there to see where things > start to fall apart. Combine with error log and there you are. > 2. "enhance" original error while unwinding the stack (add "context" > if you will). Or use trace tags. :) Goran, I think maybe we are agreeing on the same issues. Trust me, this is not an issue for me. Flows are not broken, exceptions are truly the "exception" and not the rule and I have implemented different ideas different ways, some simple, some more detailed for consumer usage. So while I may say/write one thing, its not the be taken as its the only way. :) The only point I was making is that using a catch all, to me, is generally more useful until you can chime in on specifics. Note I am not speaking of an external the code block catch alls, but at the same level. The external catch all only help you under debug mode since it has source line numbering. >> Not quite sure if I follow, and if I did, I would not be among the >> 90%. > > I really have to press you here: so what code do you have after a > failed call? Its covered. its not ignored goran. :) Again, just an example, a good template would be for boolean function: bool Function(<your parameters>) { try { // whatever return true; } catch (Exception ex) // do whats necessary or nothing. } finally { // do whats necessary or nothing } return false; } but it all depends goran, what if i wanted to have an overloads too? What if you wanted to have available a boolean return or a exception model? I have both for some functions. > Provide some examples, and I am pretty confident that, > even if you used exceptions, you could get what you need pretty easily > (that is, even if you "handle" errors, it's trivial to do what you do > either way, exceptions or error-return). Sure, I have both. A good example was a NNTP date/time parser. The syntax is: yymmdd hhmmss [GMT] RFC 977 [yy]yymmdd hhmmss [GMT] RFC 3977 (replaces RFC 977) I ended up with two parsing overloading functions public bool GetNNTPDateTime(string sdt, out DateTime dt) public DateTime GetNNTPDateTime(string sdt); so I can use the first overload: public bool onNEWGROUPS(ClientData ctcp, string args) { DateTime dtSince; if (!GetNNTPDateTime(args, dtSince)) { ctcp.SendLog("501 Invalid date/time\r\n"); return true; // RETURN ONLY FALSE TO BREAK CONNECTION } .... return true; } or I can use the exception overload version: public bool onNEWGROUPS(ClientData ctcp, string args) { DateTime dtSince; try { dtSince = GetNNTPDateTime(args) } catch (Exception ex) { ctcp.SendLog("501 {0}\r\n",ex.Message); return true; // RETURN ONLY FALSE TO BREAK CONNECTION } .... return true; } But I ended up using the latter because the parser throws string exception reasons, part of which also comes from the DateTime converter exception trap. At the risk of getting slammed (so be nice <g>), these are the functions (Keep in mind that these were ported from my C/C++ NNTP server code, which does not have the exception version). public static DateTime GetNewGroupsDate(string sdt, DateTimeKind wantKind = DateTimeKind.Utc) { // parse parts: acceptable formats: // YYMMDD HHMMSS [GMT] // YYYYMMDD HHMMSS [GMT] string[] parts = sdt.Trim().Split(' '); if (parts.Length < 2) { throw new Exception("Insufficient parameters provided"); } int len = parts[0].Length; if (len != 6 && len != 8) { throw new Exception("Illegal Date - [YY]YYMMDD required"); } int num = Convert.ToInt32(parts[0]); int wYear = num / 10000; num -= wYear * 10000; int wMonth = num / 100; num -= wMonth * 100; int wDay = num; if (len == 6) // support RFC3977 6/8 digits semantics { int Century = (DateTime.Now.Year / 100) * 100; if (wYear <= (DateTime.Now.Year - Century)) { wYear += Century; } else { wYear += Century - 100; } } if (parts[1].Length != 6) { throw new Exception("Illegal time - HHMMSS required"); } num = Convert.ToInt32(parts[1]); int wHour = num / 10000; num -= wHour * 10000; int wMinute = num / 100; num -= wMinute * 100; int wSeconds = num; bool gmt = false; if (parts.Length > 2) gmt = (parts[2].ToLower() == "gmt"); DateTime dt = new DateTime(wYear, wMonth, wDay, wHour, wMinute, wSeconds, gmt ? DateTimeKind.Utc:DateTimeKind.Local); if (dt.Kind == DateTimeKind.Local && wantKind == DateTimeKind.Utc) return dt.ToUniversalTime(); if (dt.Kind == DateTimeKind.Utc && wantKind == DateTimeKind.Local) return dt.ToLocalTime(); return dt; } public static bool GetNewGroupsDate( string sdt, out DateTime dt, DateTimeKind wantKind = DateTimeKind.Utc) { dt = DateTime.MinValue; try { dt = GetNewGroupsDate(sdt, wantKind); return true; } catch (Exception) { } return false; } > Here's my bet: most of the > time, you have error cleanup, for which you should use RAII in e.g. C+ > +, or "using" in C#, or you have logging (see above about that). In > rare places, you have "corrective" action. But I bet you that these > are __rare__. While I am moving towards .NET, nothing will be release (WCLEX is a "get a deeper feel" of what to expect project) until the proper error/memory/threading framework is worked out and well understood. Again, because .NET is richly complex and does so much for you, the developers need to get a handle of whats already done for you or not. Probably the less you know the better. :) But thats not the case here. I have language-independent expectations for well understood solid behavior. Files, sockets, etc, all work one way - how its implemented is what needs to be understood. What that means I must have my "managed" exceptions worked out but I will now under .NET, allow on "catch alls" to cover the unexpected. The thing is under .NET you almost always have catch all at the top level if you use the auto-generated main program code, so that means that your own catch all is to keep the application running for the most part, and that can hurt you too. If anything, that is the big key thing here - do you abort or gracefully recover for the unexpected catch alls? This applies to any framework and language and these ideas are 100% application implementation specific. There are no hard rule. There are better rules, but never ALL cut and dry. -- HLS
From: David Ching on 24 Jun 2010 13:43
"Joseph M. Newcomer" <newcomer(a)flounder.com> wrote in message news:avn626hdrqo5mirsnvqg38a5e32v052sd1(a)4ax.com... > It's called a "database" and I'm sure that databases (which require the > ability to append > new records) are not in the 5% category... I had not considered that. But I doubt very much that databases uses .NET StreamWriter! -- David |