Προς το περιεχόμενο

Προτεινόμενες αναρτήσεις

Δημοσ.

Αντιμετώπισα πρόβλημα με την υλοποίηση ενός layered error-handling interface (στον HexViewer, με C99, για όσους έχουν διαβάσει το νήμα περί χειροκίνητου pattern matching).

 

Εν τάχει, σχεδιαστικά ο κώδικας είναι layered και το ζητούμενο είναι όλα τα error-messages να τυπώνονται στο υψηλότερο layer, για εύκολη μελλοντική συντήρηση ή/και αναδιοργάνωση όταν χρειαστεί.

 

Οπότε, τα χαμηλότερα layers αντί να τυπώσουν άμεσα τυχόν σφάλματα που προκύπτουν, ενημερώνουν με το κατάλληλο error-code μια καθολικά ορισμένη δομή για errors. Το κάθε layer υιοθετεί όποιο error-code έχει σεταριστεί από χαμηλότερα layers (εξετάζει την καθολική δομή, κι αν είναι σεταρισμένο κάποιο error το περνάει στο παραπάνω layer), μέχρι η ροή να φτάσει στο layer που είναι υπεύθυνο για την εκτύπωση του σφάλματος (βασικά όλων των σφαλμάτων).

 

Το πρόβλημα είναι πως τα error-messages μπορούν να περιέχουν διαφορετικό πλήθος αλλά και τύπο πληροφοριών, ανάλογα με το layer στο οποίο προέκυψε αρχικά το σφάλμα.

 

Για παράδειγμα, μπορεί σε ένα σημείο ένα σφάλμα να πρέπει να ενημερώσει για εσφαλμένες τιμές ενός integer κι ενός string, ενώ σε κάποιο άλλο σημείο άλλο σφάλμα να πρέπει να ενημερώσει για εσφαλμένη τιμή ενός float, και πάει λέγοντας. Και προφανώς οι μεταβλητές αυτές μπορούν κάλλιστα να είναι τοπικές στο χαμηλό layer (άρα άγνωστες στα πιο πάνω layers).

 

Η λύση στην οποία κατέληξα σήμερα είναι η καθολική δομή των errors να περιέχει 2 πεδία: ένα id (int) και ένα msg (c-string), και κάθε φορά που προκύπτει κάποιο σφάλμα να ενημερώνεται με μια ανάθεση (στο id) κι ένα snprintf (στο msg).

 

Δηλαδή...

 

 

 

Η καθολικά ορισμένη δομή...

 

 

>
#ifndef HVERROR_H				/* start of inclusion guard */
#define HVERROR_H

#define MAXLEN_ERRMSG	(59+1)

#define VALID_ERRID(id)	( (id) > ERRDUMMY && (id) < MAXERRORS )
enum ErrId {
ERRDUMMY 	= -1,			/* ensure enums correspond to ints */
ERR_NOERROR 	= 0,
ERR_FATAL,
ERR_NOFILE,
ERR_NOEFFECT,
ERR_NOSUBCMD,
ERR_NOPARAM,
ERR_INVPARAM,
ERR_LOADFILE_NOPARAM,
ERR_SHELL_NOSHELL,
ERR_SHELL_EXITCODE,
ERR_SKIN_INVALID,
ERR_BOOK_INVLABEL,
ERR_BOOK_OFSTUSED,
ERR_BOOK_LABELUSED,
ERR_BOOK_LISTFULL,
ERR_BOOK_NONEED,
ERR_BOOK_GNOLABEL,
ERR_BOOK_GNOID,
ERR_SEQSEARCH_INVSEQ,
/* not an error-code, just their total count */
MAXERRORS
};

struct Err {
enum ErrId id;
char msg[MAXLEN_ERRMSG];
};

#ifndef HV_MAIN_C
extern struct Err g_err;
#else
struct Err g_err;
#endif

#endif						/* end of inclusion guard        	*/

 

 

 

Οι συναρτήσεις διαχείρισης της δομής των errors...

 

 

>
/*********************************************************//**
*
*************************************************************
*/
bool err_set( enum ErrId id, const char *fmttxt, ... )
{
va_list	args;

if ( !VALID_ERRID(id) || !fmttxt || !*fmttxt )
	return false;

g_err.id = id;

va_start( args, fmttxt );
vsnprintf( g_err.msg, MAXLEN_ERRMSG, fmttxt, args );
va_end( args );

return true;
}

/*********************************************************//**
*
*************************************************************
*/
enum ErrId err_get_id( void )
{
return g_err.id;
}

/*********************************************************//**
*
*************************************************************
*/
char *err_get_pmsg( void )
{
return g_err.msg;
}

/*********************************************************//**
*
*************************************************************
*/
bool err_print( const Settings *settings )
{
if ( !VALID_ERRID(g_err.id) || !settings )
	return false;

colorWARNMSG( settings, g_err.msg );
BELL(1);
pressENTER();

return true;
}

 

 

 

Απόσπασμα κώδικα από το (υψηλότερο) layer που τυπώνει τα σφάλματα...

 

 

>
static bool do_command( const char *cmd, ... )
{
enum KeyCommand key;

err_set( ERR_NOERROR, ERRMSG_NOERROR );			/* in "hverror.h" */

if ( !cmd || !buffer || !settings || !bt)
	goto ret_fatal;

/* execute the command */

key = tolower( (int) *cmd );
switch ( key )
{
	case KEY_BOOK:					/* bookmark        	*/
		if ( !do_command_book(cmd, buffer, settings, bt) )
			goto ret_fatal;
		if ( err_get_id() != ERR_NOERROR )
			err_print( settings );
		break;
...
}

return true;

ret_fatal:
err_set( ERR_FATAL, ERRMSG_FATAL );
return false;
}

 

 

 

Απόσπασμα κώδικα από ενδιάμεσο layer...

 

 

>
bool do_command_book( const char *cmd, ... )
{
...
enum KeyCommand subkey = '\0';				/* sub-command key */
int 		id = INVIDX;				/* bookmark-id 	*/
char 		*label = NULL;				/* bookmark-label  */

err_set( ERR_NOERROR, ERRMSG_NOERROR );			/* in "hverror.h "*/

/* sanity checks */
if ( !cmd || !*cmd || !buffer || !settings || !bt )
	goto ret_fatal;

/* no loaded file? */
if ( !buffer->data ) {
	err_set( ERR_NOEFFECT, ERRMSG_NOEFFECT );
	goto ret_nonfatal;
}

/* extract subkey, id and label from the user command-line  */
if ( !parse_command_book( cmd, &subkey, &id, &label ) )
	goto ret_fatal;
if ( err_get_id() != ERR_NOERROR )			
	goto ret_nonfatal;

switch( subkey )
{
	...
	case KEY2_SETBOOK: 			/* set bookmark (label)  	*/
		if (
		!do_book_set( &buffer->books, id,label, buffer, settings, *bt)
		)
			goto ret_fatal;
		break;
  	...
}
...
ret_nonfatal:
return true;

ret_fatal:
err_set( ERR_FATAL, ERRMSG_FATAL );
return false;
}

 

 

 

Και τέλος απόσπασμα από το χαμηλότερο layer που παράγει το σφάλμα...

 

 

>
static bool do_book_set(
BookList 	*books,
int		id,			/* must be INVIDX when label != NULL */
char 		*label,			/* must be NULL when id != INVIDX	*/
...
)
{
/* sanity checks */
if ( !books || !buffer )
	goto ret_fatal;

/* first & last byte cannot be bookmarked (there are explicit commands for that)*/
if ( 0 == bt || bt == buffer->len - 1 ) {
	err_set( ERR_BOOK_NONEED, ERRMSGF_BOOK_NONEED, KEY_GSOF, KEY_GEOF );
	goto ret_nonfatal;
}

/* is current byte-offset already bookmarked? */
i = booklist_lookup_bt( books, bt );
if ( i != INVIDX ) {
	err_set(ERR_BOOK_OFSTUSED, ERRMSGF_BOOK_OFSTUSED, books->list[i].label, i);
	goto ret_nonfatal;
}

/* is the list full? */
i = booklist_first_empty( books );
if ( INVIDX == i ) {
	err_set( ERR_BOOK_LISTFULL, ERRMSG_BOOK_LISTFULL );
	goto ret_nonfatal;
}

if ( label )					/* user specified a label */
{
	/* is label aleady in use? */
	if ( booklist_lookup_label( books, label ) != INVIDX ) {
		err_set( ERR_BOOK_LABELUSED, ERRMSG_BOOK_LABELUSED );
		goto ret_nonfatal;
	}

	/* at this point, i is the 1st non-empty pos of the bookmark-list */
	if ( !booklist_iSet_label(books, i, label, bt) )
		goto ret_fatal;

	id = i;

}
else if ( INVIDX != id )			/* user specified an id instead */
...
ret_nonfatal:
return true;

ret_fatal:
err_set( ERR_FATAL, ERRMSG_FATAL );
return false;
}

 

 

 

 

 

 

Το παραπάνω δουλεύει, αλλά θέλω να μειώσω το overhead.

 

Ιδανικά θα ήθελα να υπάρχει ένας πίνακας από pre-defined messages (τα οποία είναι έτσι κι αλλιώς predefined και τώρα, δείτε το spoiler που ακολουθεί), ο οποίος θα γίνεται απευθείας index με το error-code.

 

Predefined error-messages...

 

 

>
...
/* Error Messages ==================================================================== */

#define ERRMSG_NOERROR		"Κανένα σφάλμα! "
#define ERRMSG_FATAL		"Μοιραίο, εσωτερικό σφάλμα! Τερματισμός προγράμματος... "
#define ERRMSG_NOPARAM		\
"Η εντολή αυτή χρειάζεται μια ή περισσότερες παραμέτρους! "
#define ERRMSG_NOEFFECT		"Εντολή χωρίς αντίκρισμα ( υπάρχει ανοιχτό αρχείο; )! "
#define ERRMSG_NOSUBCMD		"Ημιτελής εντολή! "
#define ERRMSG_INVPARAM		"Αντικανονική παράμετρος ή υπερχείλιση! "

#define ERRMSG_LOADFILE_NOPARAM	"Δεν δώσατε όνομα αρχείου! "
#define ERRMSG_NOFILE		"Ανύπαρκτο αρχείο, διατήρηση τρέχοντος! "
#define ERRMSGF_SKIN_INVALID	"Κωδικοί έγκυρων προσόψεων: %hd έως %hd "

#define ERRMSG_SHELL_NOSHELL	"Το σύστημά σας δεν διαθέτει φλοιό/γραμμή εντολών! "
#define ERRMSGF_SHELL_EXITCODE	"κωδικός αποτυχίας εντολής συστήματος: %d "

#define ERRMSG_BOOK_INVLABEL	\
"Οι ετικέτες σελιδοδεικτών αρχίζουν πάντα με λατινικό γράμμα! "
#define ERRMSGF_BOOK_OFSTUSED	"Υπάρχει ήδη ως \"%s\" με κωδικό %d "
#define ERRMSG_BOOK_LABELUSED	"Η ετικέτα αυτή χρησιμοποιείται ήδη! "
#define ERRMSG_BOOK_LISTFULL	"Η λίστα είναι γεμάτη, σβήσετε μερικούς σελιδοδείκτες! "
#define ERRMSGF_BOOK_NONEED	"Δεν χρειάζεται! Δοκιμάστε τις εντολές %c ή %c "
#define ERRMSGF_BOOK_GNOLABEL	\
"Ανύπαρκτη ετικέτα: %s (%c%c για προβολή λίστας)! "
#define ERRMSGF_BOOK_GNOID	\
"Ανύπαρκτος κωδικός σελιδοδείκτη (%c%c για προβολή λίστας)! "

#define ERMSG_SEQSEARCH_INVSEQ	\
"Εσφαλμένη ακολουθία (απαιτείται ζυγό πλήθος 16-δικών ψηφίων) "
...

 

 

Αν δεν υπήρχε το εμπόδιο των παραμέτρων, θα μπορούσε να γίνει κάπως έτσι...

 

 

 

>
#ifndef HVERROR_H				/* start of inclusion guard */
#define HVERROR_H

#include "hvlang.h"				/* for the error-messages */

enum ErrId {
ERRDUMMY 	= -1,			/* ensure enums correspond to ints */
ERR_NOERROR 	= 0,
ERR_FATAL,
ERR_NOFILE,
ERR_NOEFFECT,
ERR_NOSUBCMD,
ERR_NOPARAM,
ERR_INVPARAM,
ERR_LOADFILE_NOPARAM,
ERR_SHELL_NOSHELL,
ERR_SHELL_EXITCODE,
ERR_SKIN_INVALID,
ERR_BOOK_INVLABEL,
ERR_BOOK_OFSTUSED,
ERR_BOOK_LABELUSED,
ERR_BOOK_LISTFULL,
ERR_BOOK_NONEED,
ERR_BOOK_GNOLABEL,
ERR_BOOK_GNOID,
ERR_SEQSEARCH_INVSEQ,
/* not an error-code, just their total count */
MAXERRORS
};
#define VALID_ERRID(id)	( (id) > ERRDUMMY && (id) < MAXERRORS )

#ifndef HV_MAIN_C
extern int	g_error;
extern char 	*g_errmsg[];

#else
int g_error;
char *g_errmsg[] = {
	/* ERR_NOERROR      	*/	"No error ",
	/* ERR_FATAL        	*/	ERRMSG_FATAL,
	/* ERR_NOFILE   		*/ 	ERRMSG_NOFILE,
	/* ERR_NOEFFECT 		*/	ERRMSG_NOEFFECT,
	/* ERR_NOSUBCMD 		*/	ERRMSG_NOSUBCMD,
	/* ERR_NOPARAM      	*/	ERRMSG_NOPARAM,
	/* ERR_INVPARAM 		*/	ERRMSG_INVPARAM,
	/* ERR_LOADFILE_NOPARAM */	ERRMSG_LOADFILE_NOPARAM,
	/* ERR_SHELL_NOSHELL	*/	ERRMSG_SHELL_NOSHELL,
	/* ERR_SHELL_EXITCODE   */	ERRMSGF_SHELL_EXITCODE,
	/* ERR_SKIN_INVALID 	*/	ERRMSGF_SKIN_INVALID,
	/* ERR_BOOK_INVLABEL	*/	ERRMSG_BOOK_INVLABEL,
	/* ERR_BOOK_OFSTUSED	*/	ERMSGF_BOOK_OFSTUSED,
	/* ERR_BOOK_LABELUSED   */	ERRMSG_BOOK_LABELUSED,
	/* ERR_BOOK_LISTFULL	*/	ERRMSG_BOOK_LISTFULL,
	/* ERR_BOOK_NONEED  	*/	ERRMSGF_BOOK_NONEED,
	/* ERR_BOOK_GNOLABEL	*/	ERRMSGF_BOOK_GNOLABEL,
	/* ERR_BOOK_GNOID   	*/	ERRMSGF_BOOK_GNOID,
	/* ERR_SEQSEARCH_INVSEQ */	ERMSG_SEQSEARCH_INVSEQ
};
#endif

#endif						/* end of inclusion guard        	*/

 

 

Οπότε κάθε ρουτίνα που παράγει σφάλμα θα έκανε μια απλή ανάθεση στο g_error και η ρουτίνα εκτύπωσης απλώς θα τύπωνε το g_errmsg[ g_error ];

 

Δεν μου έρχεται όμως κάποια efficient δομή του πίνακα, που σε κάθε στοιχείο του εκτός από το message θα περιέχει και τυχόν τιμές παραμέτρων που θα ενσωματώνονται στο message. Κάτι εναλλακτικό δηλαδή της snprintf() που χρησιμοποιεί τώρα η err_set() που θα είναι ταχύτερο.

 

Βασικά γίνεται ή τσάμπα παιδεύομαι και να το αφήσω έτσι; Έχετε υπόψη σας κανέναν πιο efficient (αλλά γενικό) τρόπο για να αντικαταστήσω το παραπάνω;

Δημοσ.

Λυση 1 πας σε μια γλωσσα με exceptions

Λυση 2 κανε αυτο που κανουν τα win. Μια global μεταβλητη που θα εχει το τελευταιο error σε enum/int οχι string, και παραλληλα στο documentation του προγραμματος θα εχεις μια σελιδα που θα επεξηγει τα errors. Ετσι πχ ο πανω layer καλει τον κατω και ο κατω σου επιστρεφει σφαλμα χ ετσι ο πανω layer θα ειναι γραμμενος συμφωνα με το documentation.

Δημοσ.

Λυση 1 πας σε μια γλωσσα με exceptions

Λυση 2 κανε αυτο που κανουν τα win. Μια global μεταβλητη που θα εχει το τελευταιο error σε enum/int οχι string, και παραλληλα στο documentation του προγραμματος θα εχεις μια σελιδα που θα επεξηγει τα errors. Ετσι πχ ο πανω layer καλει τον κατω και ο κατω σου επιστρεφει σφαλμα χ ετσι ο πανω layer θα ειναι γραμμενος συμφωνα με το documentation.

Εντάξει, το 1ο δεν παίζει γιατί έχω φάει ήδη τον γάιδαρο κι έχει μείνει η ουρά (εννοώ έχουν ήδη γραφτεί τα 4/5 του προγράμματος σε C).

 

Το 2ο δεν το κατάλαβα ρε συ. Το πρόβλημά μου είναι πως τα string messages που αντιστοιχούν στο κάθε enum/int error-code δεν είναι ομοιογενή... δηλαδή, άλλα είναι απλά texts., άλλα όμως πρέπει να τυπώσουν μέσα τους τιμές από ανομοιογενές πλήθος και είδος μεταβλητών.

 

Και τώρα με enum/int τα διαχειρίζομαι τα errors (με το id τους δηλαδή) αλλά λόγω του παραπάνω αναγκάζομαι και καλώ συνέχεια την vsnprintf() ( μέσω της err_set() ) ακόμα και για errors των οποίων το αντίστοιχα message είναι απλώς text ('απλώς text' = χωρίς %d %c %s μέσα τους).

Δημοσ.
[..]

Το 2ο δεν το κατάλαβα ρε συ. Το πρόβλημά μου είναι πως τα string messages που αντιστοιχούν στο κάθε enum/int error-code δεν είναι ομοιογενή... δηλαδή, άλλα είναι απλά texts., άλλα όμως πρέπει να τυπώσουν μέσα τους τιμές από ανομοιογενές πλήθος και είδος μεταβλητών.

 

Και τώρα με enum/int τα διαχειρίζομαι τα errors (με το id τους δηλαδή) αλλά λόγω του παραπάνω αναγκάζομαι και καλώ συνέχεια την vsnprintf() ( μέσω της err_set() ) ακόμα και για errors των οποίων το αντίστοιχα message είναι απλώς text ('απλώς text' = χωρίς %d %c %s μέσα τους).

 

Σεβόμενος τους περιορισμούς (όχι Exceptions κλπ). Θα μπορούσες ενδεχομένως να κατασκευάσεις μια δομή (πχ. Err) όπου θα υπάρχει ένα ID (.Type), ενδεχομένως ένα char* (.Msg, ένα "γενικό" μήνυμα σφάλματος) και ένα void* (.Data) για τα έξτρα δεδομένα σου και ύστερα με βάση το ID θα κάνει τα ανάλογα cast ώστε να διαχειριστείς όπως θεωρείς καλύτερα (όποιο Layer θέλει να ασχοληθεί περισσότερο). Δεν ξέρω όμως αν αξίζει τόσος κόπος.

Δημοσ.

Σεβόμενος τους περιορισμούς (όχι Exceptions κλπ). Θα μπορούσες ενδεχομένως να κατασκευάσεις μια δομή (πχ. Err) όπου θα υπάρχει ένα ID (.Type), ενδεχομένως ένα char* (.Msg, ένα "γενικό" μήνυμα σφάλματος) και ένα void* (.Data) για τα έξτρα δεδομένα σου και ύστερα με βάση το ID θα κάνει τα ανάλογα cast ώστε να διαχειριστείς όπως θεωρείς καλύτερα (όποιο Layer θέλει να ασχοληθεί περισσότερο). Δεν ξέρω όμως αν αξίζει τόσος κόπος.

Αυτό είχα σκεφτεί κι εγώ φίλε DirectX. Πέρα όμως από την (πολλή) παραπάνω δουλειά που χρειάζεται, δεν είμαι και καθόλου σίγουρος για το αν τελικά θα εκτελείται ταχύτερα από το τωρινό (μάλλον πιο αργό θα είναι). Για αυτό απευθύνθηκα στο φόρουμ, για ιδέες κι εναλλακτικούς τρόπους.

 

Μάλλον δεν πρέπει να υπάρχει κάτι όμως, ε;

 

Ευχαριστώ για το πολύ ενδιαφέρον link φίλε cgk, clean & pretty φαίνεται η βιβλιοθήκη... την κατέβασα ήδη, αλλά για μελλοντική χρήση (μιας και πρέπει να αλλάξει τελείως η λογική όλου του προγράμματος για να τη χρησιμοποιήσω). Παρεμπιπτόντως, εδώ είναι μια άλλη πολύ ενδιαφέρουσα υλοποίηση exceptions σε C, με πολύ λίγο overhead και καθόλου heap usage (είναι φτιαγμένη για embedded και χρησιμοποιεί τον native, αλλά primitive, μηχανισμό της C για αυτές τις δουλειές... setjmp & longjmp: http://www.on-time.com/ddj0011.htm).

Δημοσ.

Αυτό είχα σκεφτεί κι εγώ φίλε DirectX. Πέρα όμως από την (πολλή) παραπάνω δουλειά που χρειάζεται, δεν είμαι και καθόλου σίγουρος για το αν τελικά θα εκτελείται ταχύτερα από το τωρινό (μάλλον πιο αργό θα είναι). Για αυτό απευθύνθηκα στο φόρουμ, για ιδέες κι εναλλακτικούς τρόπους.

 

Μάλλον δεν πρέπει να υπάρχει κάτι όμως, ε;

 

Ευχαριστώ για το πολύ ενδιαφέρον link φίλε cgk, clean & pretty φαίνεται η βιβλιοθήκη... την κατέβασα ήδη, αλλά για μελλοντική χρήση (μιας και πρέπει να αλλάξει τελείως η λογική όλου του προγράμματος για να τη χρησιμοποιήσω). Παρεμπιπτόντως, εδώ είναι μια άλλη πολύ ενδιαφέρουσα υλοποίηση exceptions σε C, με πολύ λίγο overhead και καθόλου heap usage (είναι φτιαγμένη για embedded και χρησιμοποιεί τον native, αλλά primitive, μηχανισμό της C για αυτές τις δουλειές... setjmp & longjmp: http://www.on-time.com/ddj0011.htm).

Φίλε migf1 εγώ θα έβλεπα το ζήτημα συνολικά από άλλη οπτική γωνία. Θα έλεγα λοιπόν, αξίζει όλος αυτός ο κόπος (layers κλπ) για το συγκεκριμένο λογισμικό; Μήπως κάνω άθελα μου overengineering ένα κατά τεκμήριο απλό προϊόν; Μήπως θα ήταν καλύτερα να εφάρμοζα μια K-I-S-S λογική κατά την σχεδίαση και ανάπτυξη του; Ο προβληματισμός μου προέρχεται από προσωπικές εμπειρίες αλλά θεωρώ ότι ίσως φανεί χρήσιμος στην κουβέντα μας.

Δημοσ.

Καλημέρα,

 

για απλό το ξεκίνησα, αλλά γινόταν σταδιακά δυσκολότερο να παρακολουθήσω τον ίδιο μου τον κώδικα όσο προχωρούσε. Οπότε σε κάποια φάση έκατσα και τον χώρισα σε αρχεία, μην φανταστείς όμως πως τα layers είναι σαφώς διατυπωμένα στις ονομασίες των αρχείων. Ρόλο στην αύξηση του κώδικα παίζουν και χαρακτηριστικά εξωγενή από τον πυρήνα, όπως είναι για παράδειγμα η cross-platform υποστήριξη χρωμάτων και προσόψεων, όπως και η πρόβλεψη για πολλές γλώσσες (παρόλο που αυτό το τελευταίο υποστηρίζεται μονάχα σε επίπεδο προγραμματιστή και όχι απλού χρήστη... για να διατηρηθεί προγραμματιστικά πιο απλό).

 

Πολύ χρόνο ανάλωσα και στη διαχείριση της εσωτερικής γραμμής εντολών, η οποία σε ρυθμό κονσόλας μπορεί να αποτελέσει πηγή πολλών κρασαρισμάτων, αν δεν ελέγχεται λεπτομερώς το user input (κάτι που π.χ. στα GUI είναι πολύ πιο απλό, μιας και αυτά παρέχουν έτοιμες μάσκες αποδεκτού input).

 

Σε LL γλώσσες o κανόνας είναι αυτό που λες κι εσύ, η απλότητα, όταν όμως καλούνται να υλοποιήσουν πιο HL λειτουργίες η απλή προσέγγιση μπορεί τελικά να παράξει πιο πολύπλοκο κώδικά.

 

Δες για παράδειγμα τη ρουτίνα που τυπώνει χρωματισμένη μια 16-αδική γραμμή στον HexViewer...

 

 

 

>
/*********************************************************//**
* List the contents of a row in hex/char format.
*************************************************************
*/
static bool
draw_row(
uintmax_t 	row,
uintmax_t 	btcurr,
const Buffer 	*buffer,
const Settings 	*settings
)
{
char clead;					/* leading displayed char 	*/
uintmax_t i;
const uintmax_t row2idx = row * FMT_NCOLS;

if ( !buffer || row2idx > buffer->len )
	return false;

/* no file loaded? */
if ( !buffer->data )
	return true;

/* row offset */
clead 	= row == btcurr / FMT_NCOLS ? '*' : ' ';
if ( row == btcurr / FMT_NCOLS )
	colorPRINTF(
		settings->colorize,
		settings->skin.bytcurr.fg, settings->skin.bytcurr.bg,
		"%c%0*lX ", clead, FMT_OFST,(long unsigned)row2idx
	);
else
	colorPRINTF(
		settings->colorize,
		settings->skin.rowofst.fg, settings->skin.rowofst.bg,
		"%c%0*lX ", clead, FMT_OFST,(long unsigned)row2idx
	);

/* hex pane */
for (i=0; i < FMT_NCOLS && row2idx + i < buffer->len; i++)
{
	char *fmttext		= (btcurr == row2idx + i) ? "%02hX" : "%02hX";
	char ccurs		= (btcurr == row2idx + i) ? FMT_TXTCURSOR : ' ';
	Byte byte 		= buffer->data[row2idx + i];
	const _Bool isPrintable = (settings->charset == FMT_ASCII)
				? isprint((int)byte)
				: byte > 31;

	if ( i != 0 && i % FMT_GRPCOLS == 0 )		/* group separator */
		colorPRINTF(
			settings->colorize,
			settings->skin.normal.fg, settings->skin.normal.bg,
			" " );

	if ( isPrintable )				/* printable byte */
	{
		if ( btcurr != row2idx + i )
			colorPRINTF(
				settings->colorize,
				settings->skin.bytprt1.fg, settings->skin.bytprt1.bg,
				fmttext, byte );
		else
			colorPRINTF(
				settings->colorize,
				settings->skin.bytcurr.fg, settings->skin.bytcurr.bg,
				fmttext, byte );
	}
	else if ( byte == 0 )				/* zeroed byte    	*/
	{
		if ( btcurr != row2idx + i )
			colorPRINTF(
				settings->colorize,
				settings->skin.bytzero.fg, settings->skin.bytzero.bg,
				fmttext, byte );
		else
			colorPRINTF(
				settings->colorize,
				settings->skin.bytcurr.fg, settings->skin.bytcurr.bg,
				fmttext, byte );
	}
	else						/* non-printable byte */
	{
		if ( btcurr != row2idx + i )
			colorPRINTF(
				settings->colorize,
				settings->skin.bytprt0.fg, settings->skin.bytprt0.bg,
				fmttext, byte );
		else
			colorPRINTF(
				settings->colorize,
				settings->skin.bytcurr.fg, settings->skin.bytcurr.bg,
				fmttext, byte );
	}

	/* put the text cursor next to current byte */
	colorPRINTF(settings->colorize, settings->skin.rowofst.fg,settings->skin.rowofst.bg, "%c", ccurs);
}

/* if in last row, fill with blanks any leftovers until the ascii pane */
for (; i < FMT_NCOLS; i++) {
	if (i != 0 && i % FMT_GRPCOLS == 0 )		/* group separator   */
		colorPRINTF(
			settings->colorize,
			settings->skin.normal.fg, settings->skin.normal.bg,
			" ");
	colorPRINTF(					/* space for normal bt*/
		settings->colorize,
		settings->skin.normal.fg, settings->skin.normal.bg,
		"   " );
}

/* panes separator (special care if txt-cursor was at row's last-byte) */
if ( btcurr == row2idx + (--i) )
	colorPRINTF(
		settings->colorize,
		settings->skin.normal.fg, settings->skin.normal.bg,
		" " );
else {
	putchar('\b');
	colorPRINTF(
		settings->colorize,
		settings->skin.normal.fg, settings->skin.normal.bg,
		"  " );
}

/* ascii pane */
for (i=0; i < FMT_NCOLS && row2idx + i < buffer->len; i++)
{
	int c = (int)buffer->data[row2idx + i];
	const _Bool isPrintable = (settings->charset == FMT_ASCII)
				? isprint(c)
				: (c > 31);

	if ( isPrintable )				/* printable byte 	*/
	{
		if ( btcurr == row2idx + i )		/* current byte */
			colorPRINTF(
				settings->colorize,
				settings->skin.bytcurr.fg, settings->skin.bytcurr.bg,
				"%c", c );
		else				
			colorPRINTF(
				settings->colorize,
				settings->skin.bytprt1.fg, settings->skin.bytprt1.bg,
				"%c", c );
	}
	else if ( c == '\0' )				/* zero-byte char 	*/
	{
		if ( btcurr == row2idx + i )		/* current byte */
			colorPRINTF(
				settings->colorize,
				settings->skin.bytcurr.fg, settings->skin.bytcurr.bg,
				"." );
		else
			colorPRINTF(
				settings->colorize,
				settings->skin.bytzero.fg, settings->skin.bytzero.bg,
				"." );
	}
	else						/* non-printable char */
	{
		if ( btcurr == row2idx + i )		/* current byte */
			colorPRINTF(
				settings->colorize,
				settings->skin.bytcurr.fg, settings->skin.bytcurr.bg,
				"." );
		else
			colorPRINTF(
				settings->colorize,
				settings->skin.bytprt0.fg, settings->skin.bytprt0.bg, "." );
	}
}
for (; i < FMT_NCOLS; i++)
//		putchar(' ');
		colorPRINTF(
			settings->colorize,
			settings->skin.normal.fg, settings->skin.normal.bg,
			" " );
putchar('\n');

return true;
}

 

 

Ακριβώς επειδή ξεκίνησε (κι έχει παραμείνει) με απλοϊκή προσέγγιση, όχι μόνο είναι μακρινάρι αλλά δεν είναι και καθόλου ευέλικτη. Με πιο advanced σχεδιασμό εξαρχής θα μπορούσε αφενός να είναι η μισή σε κώδικα και αφετέρου να είναι πανεύκολο να προστεθεί π.χ η περίπτωση διαφορετικού χρωματισμού σε byte που είναι bookmarked.

 

Πιο advanced σχεδιασμός σε αυτή την περίπτωση, σημαίνει το κάθε byte του buffer να μην είναι απλά ένα uint8_t αλλά μια δομή που θα περιγράφει (και) την τρέχουσα κατάστασή του (π.χ isprintable, iszeroed, iscurrent, isbooked, κλπ) η οποία θα ενημερώνεται real time, αντί να γίνονται κάθε φορά οι υπολογισμοί που κάνει τώρα η ρουτίνα σε ΚΑΘΕ τύπωμα ενός byte στην οθόνη.

 

Τώρα θα πρέπει να το κάνω έτσι κι αλλιώς, γιατί όπως είναι δεν μπορώ να χρωματίσω bytes που είναι bookmarked χωρίς να προσθέσω κι άλλον υπολογισμό μέσα στην ρουτίνα (που για ΚΑΘΕ byte θα πρέπει να το συγκρίνει με τη λίστα των bookmarks... απαράδεκτο!).

 

Η αλλαγή της δομής του byte θα επιφέρει αλλαγές σε περισσότερο από το 60% του υπάρχοντος κώδικα.

 

Πράγματα σαν το παραπάνω είναι που δυσχέραιναν σταδιακά την περαιτέρω ανάπτυξη του (αρχικά απλοϊκού) κώδικα, και αναγκάστηκα να κάνω αναδιοργάνωση σε layers (και μερικά άλλα). Τώρα με τα bookmarks θα χρειαστεί να ξανακάνω

 

Προς το παρόν βρίσκομαι εδώ...

 

 

 

>
ls -l lang/* *.c *.h *.mak

-rw-r--r-- 1 Dell Administrators 6.9K Mar 19 22:06 lang/hvlang_el.h
-rw-r--r-- 1 Dell Administrators 6.6K Mar 19 22:08 lang/hvlang_en.h
-rw-r--r-- 1 Dell Administrators 7.4K Mar 16 02:32 con_color.h
-rw-r--r-- 1 Dell Administrators  36K Mar 19 23:21 do.c
-rw-r--r-- 1 Dell Administrators  35K Mar 19 23:21 hv.c
-rw-r--r-- 1 Dell Administrators 1.2K Mar 16 17:47 hv.h
-rw-r--r-- 1 Dell Administrators 4.1K Mar 18 12:02 hvcolor.h
-rw-r--r-- 1 Dell Administrators 3.7K Mar 19 20:13 hvdefs.h
-rw-r--r-- 1 Dell Administrators  953 Mar 19 22:42 hverror.h
-rw-r--r-- 1 Dell Administrators 1.3K Mar 14 14:19 hvlang.h
-rw-r--r-- 1 Dell Administrators 3.6K Mar 19 23:21 hvproto.h
-rw-r--r-- 1 Dell Administrators 2.7K Mar 19 20:35 hvtypes.h
-rw-r--r-- 1 Dell Administrators  413 Mar 16 15:19 makefile.mak
-rw-r--r-- 1 Dell Administrators  412 Mar 16 02:35 pomake32.mak
-rw-r--r-- 1 Dell Administrators  419 Mar 14 17:49 pomake64.mak
-rw-r--r-- 1 Dell Administrators  14K Mar 15 11:46 skin.c
-rw-r--r-- 1 Dell Administrators  14K Mar 19 23:20 util.c
-rw-r--r-- 1 Dell Administrators 3.5K Mar 14 14:20 w32con_cpout.h

 

 

και... συνεχίζω :lol:

 

ΥΓ. Εννοείται πως ιδέες και προτάσεις για βελτίωση είναι παραπάνω από ευπρόσδεκτες!

Δημοσ.

Ρε συ migf1...

 

 

Θα σου έλεγα για error handling... αλλά δεν είχα ποτέ την ανάγκη να το κάνω. Τα δικά μου δεν κάνουν λάθη!

Επίσης, είναι τόσο ανεπτυγμένα που έχουν δική τους νοημοσύνη. Αποφασίζουν πότε θα τρέξουν κτλ.

 

:D :D :D :D

Δημοσ.

Ρε συ migf1...

 

 

Θα σου έλεγα για error handling... αλλά δεν είχα ποτέ την ανάγκη να το κάνω. Τα δικά μου δεν κάνουν λάθη!

Επίσης, είναι τόσο ανεπτυγμένα που έχουν δική τους νοημοσύνη. Αποφασίζουν πότε θα τρέξουν κτλ.

 

:D :D :D :D

 

:lol:

 

Τους βγάζεις όμως ανοιχτούς τους δικούς σου κώδικες; (εννοώ ελεύθερους να τους πειράξουν όλοι ;) )

Δημοσ.

Μα τους πειράζουν όλοι...

 

 

Να δεις τι ακούνε οι άμοιροι :P

 

Όλο σφυρίγματα και τα σχετικά!

 

:D :D

 

 

Εάν δε είναι και τίποτα για μεταφορά δεδομένων... πέφτει και καν'να "Πως κουνιέσαι έτσι, έτσι κουνιόταν η Καλαμάτα και έπεσε" (το οποίο βέβαια... εγώ λέω πως αναφέρεται στο λειτουργικό... αλλά τι να πει κανείς; )

 

:P :D

Δημοσ.

 

 

Το 2ο δεν το κατάλαβα ρε συ. Το πρόβλημά μου είναι πως τα string messages που αντιστοιχούν στο κάθε enum/int error-code δεν είναι ομοιογενή... δηλαδή, άλλα είναι απλά texts., άλλα όμως πρέπει να τυπώσουν μέσα τους τιμές από ανομοιογενές πλήθος και είδος μεταβλητών.

 

Και τώρα με enum/int τα διαχειρίζομαι τα errors (με το id τους δηλαδή) αλλά λόγω του παραπάνω αναγκάζομαι και καλώ συνέχεια την vsnprintf() ( μέσω της err_set() ) ακόμα και για errors των οποίων το αντίστοιχα message είναι απλώς text ('απλώς text' = χωρίς %d %c %s μέσα τους).

 

Διαχειριζεσαι τα ερορ με στρινγκς. Αυτο που λεω ειναι να διαχειριζεσαι τα ερορ μονον με enum/int και την επεξηγηση να την γραφεις στο UI συμφωνα με το documentation και παντα συμφωνα με την κριση σου. Που σε βοηθαει αυτο, εστω εχεις την συναρτηση

int ConnectToServer(...)

αυτη εχει καμια 10 σφαλματα που σχετιζονται με την συνδεση, καμια 10ρια σφαλματα για την αποριψη του σερβερ κλπ

 

Οταν εισαι στο πανω επιπεδο δεν σε ενδιαφερει να αναλυσεις το σφαλμα. Πχ για τα σφαλματα "δεν υπαρχει dns", "δεν μπορει να επικοινωνησει με τον isp" και "δεν υπαρχει ιντερνετ" θα ενημεροσεις τον χρηστη με το αυτο το σφαλμα "Προβλημα δικτυου". Οπως και για την αποριψη σε περιπτωση "λαθος user" και "λαθος pass" πετας "λαθος στοιχεια εισοδου"

 

Βεβαια υπαρχει η περιπτωση να θελεις διαμορφωμενο μηνυμα σφαλματος πχ ενας parser. Εκει μπορεις να φτιαξεις μια δευτερη συνρατηση για error πχ ExtendLastError ή στην ιδια την συναρτηση να βαλεις +1 arg που θα επιστρεφει το εν λογο σφαλμα. Λογο οτι αυτες οι συναρτησεις ειναι λιγες δεν υπαρχει λογος να τινκγαρεις ολο το .text με σφαλματα για την πρωτη περιπτωση.

 

Ενα παραδειγμα ειναι το sqlite3_execπου κανει parse αρα σε περιπτωση σφαλματος στην συνταξη του query θα πρεπει να δημιουργησει δυναμικα το μηνυμα σφαλματος

 

Και το αλλο παραδειγμα ειναι το sqlite3_step που δεν θελει δυναμηκο σφαλμα και ετσι βολευεσαι με το last error

Δημοσ.

Διαχειριζεσαι τα ερορ με στρινγκς. Αυτο που λεω ειναι να διαχειριζεσαι τα ερορ μονον με enum/int και την επεξηγηση να την γραφεις στο UI συμφωνα με το documentation και παντα συμφωνα με την κριση σου.

Δεν τα διαχειρίζομαι ως strings τα errors, με error-id τα διαχειρίζομαι.

 

Παράδειγμα, όταν τα ελέγχω κάνω...

> if ( ERR_NOERROR != err_get_id() )...

και όχι...

>if ( !strcmp( ERRMSG_NOERROR, err_get_msg() ) ... 

 

...

Βεβαια υπαρχει η περιπτωση να θελεις διαμορφωμενο μηνυμα σφαλματος πχ ενας parser. Εκει μπορεις να φτιαξεις μια δευτερη συνρατηση για error πχ ExtendLastError ή στην ιδια την συναρτηση να βαλεις +1 arg που θα επιστρεφει το εν λογο σφαλμα. Λογο οτι αυτες οι συναρτησεις ειναι λιγες δεν υπαρχει λογος να τινκγαρεις ολο το .text με σφαλματα για την πρωτη περιπτωση.

 

Ενα παραδειγμα ειναι το sqlite3_execπου κανει parse αρα σε περιπτωση σφαλματος στην συνταξη του query θα πρεπει να δημιουργησει δυναμικα το μηνυμα σφαλματος

...

Αυτό είναι που χρειάζομαι, και είναι περίπου αυτό που κάνω κι εγώ αυτή τη στιγμή. Η κάθε (χαμηλή) ρουτίνα που πιάνει ένα σφάλμα, το παράγει με την err_set() χρησιμοποιώντας όποια τοπική μεταβλητή χρειάζεται να περάσει μέσα στο σφάλμα. Αντί όμως να το έχει ως παράμετρο το message του σφάλματος, και να το κάνει malloc, το γράφει απευθείας στην global struct Err (και προφανώς πριν από το message περνάει στην struct το id του σφάλματος). Αυτό έχει ως υπέρ πως α) δεν χρειάζονται malloc & free για τη δημιουργία του message β) δεν χρειάζονται όλες οι συναρτήσεις μια έξτρα παράμετρο αλλά έχει το κατά πως ΔΕΝ είναι multi-thread friendly.

 

Κατόπιν διακόπτει την ροή της κι επιστρέφει. Ο caller της (π.χ. μια ρουτίνα του από πάνω layer) πριν την καλέσει μηδενίζει το Err struct, την καλεί και αμέσως μετά εξετάζει το enumed id του global struct Err. Αν είναι διάφορο του enumed id ERR_NOERROR επιστρέφει με την σειρά της στον δικό της caller, και πάει λέγοντας.

 

Φτάνοντας με αυτό τον τρόπο στο υψηλότερο layer, η ρουτίνα ελέγχου/εκτύπωσης κοιτάει το enumed id του global struct Err και...

 

α) αν είναι ERR_FATAL, διακόπτει κι επιστρέφει στην main (η οποία κάνει cleanup και τερματίζει)

β) αν είναι διάφορο του ERR_NOERROR τυπώνει το σφάλμα που βρίσκει μέσα στο struct Err, και συνεχίζει (προφανώς κάνοντας recover)

γ) αν είναι ίσο με ERR_NOERROR, τότε απλά συνεχίζει.

 

Αντίθετα δηλαδή με την sqlite3_exec() που δέχεται ως όρισμα το message για να το τυπώσει άμεσα, εγώ δεν τυπώνω άμεσα το error στην χαμηλή ρουτίνα. Το παράγω χαμηλά και μετά το "κουβαλάω" all the way μέχρι το πάνω-πάνω layer, όπου και το τυπώνω (σε επίπεδο λογικής το εννοώ το "κουβαλάω", δεν το περνάω ως όρισμα σε όλες τις ενδιάμεσες ρουτίνες) .

 

Το ζητούμενο όλης αυτής της ιστορίας είναι να μπορώ να ελέγχω ομοιογενώς πιθανά σφάλματα του προγράμματος σε ένα μόνο σημείο, σε υψηλό επίπεδο, αντί να ψάχνομαι σε 200 μεριές.

 

Τώρα που το ανέλυσα πιο διεξοδικά, είναι πιο κατανοητό; Γιατί έχω την εντύπωση πως το Νο 2 που μου προτείνεις δεν με βοηθάει σε κάτι. Έτσι είναι ή λες κάτι άλλο (που δεν το έχω πιάσει ακόμα; )

Δημοσ.

Θα μπορούσες να έχεις δύο callback tables - function pointer arrays. Ένα για τα εισερχόμενα και ένα για τις αντιστοιχίες τους.

 

Αυτά, θα τα διαχειρίζεται μία ρουτίνα, η οποία θα παίρνει σαν όρισμα το index της εισερχόμενης ρουτίνας και τα αντίστοιχα ορίσματα.

Αυτή η ρουτίνα θα κάνει την διαχείριση του error και εάν είναι error free θα καλεί το callback με το αντίστοιχο index από τις αντιστοιχίες.

 

Προϋπόθεση το να είναι με την ίδια σειρά οι ρουτίνες στα εισερχόμενα με τις αντιστοιχείς.

Δημοσ.

Για εξήγησέ το λίγο παραπάνω, γιατί τώρα είμαι εγώ λιώμα.

 

Εννοείς ξεχωριστή ρουτίνα εκτύπωσης για κάθε ξεχωριστό message;

 

>
void print_noerror( const char *msg );
void print_fatal( const char *msg, int param1, float param2 );

enum ErrID {
ERR_NOERROR = 0,
ERR_FATAL,
...
};

enum ErrId g_error;

void (*errprt[])(const char *fmttxt, ... ) = {	/* jump table of variadic functions */
&print_noerror,
&print_fatal
...
}

Αυτό βέβαια είναι ένα call-table

 

EDIT:

 

Μπα, άκυρο... το παραπάνω πάλι έχει το πρόβλημα των παραμέτρων στις κλήσεις των...

 

> errprt[ g_error ]( ???? );

οπότε προφανώς κάτι άλλο εννοείς (για πες, γιατί δεν την παλεύω και πολύ τώρα... ήδη το έχω κάνει 10 edit το post :P).

Δημιουργήστε ένα λογαριασμό ή συνδεθείτε για να σχολιάσετε

Πρέπει να είστε μέλος για να αφήσετε σχόλιο

Δημιουργία λογαριασμού

Εγγραφείτε με νέο λογαριασμό στην κοινότητα μας. Είναι πανεύκολο!

Δημιουργία νέου λογαριασμού

Σύνδεση

Έχετε ήδη λογαριασμό; Συνδεθείτε εδώ.

Συνδεθείτε τώρα
  • Δημιουργία νέου...