Semicolons, αφαίρεση και προσθήκη

Κώδικας, πληροφορίες, ερωτήσεις και απαντήσεις σχετικές με την JavaScript.

Συντονιστές: WebDev Moderators, Super-Moderators

Απάντηση
Άβαταρ μέλους
skeftomilos
Script Master
Δημοσιεύσεις: 2888
Εγγραφή: 07 Ιαν 2005 07:22
Τοποθεσία: Αθήνα

Semicolons, αφαίρεση και προσθήκη

Δημοσίευση από skeftomilos » 19 Ιούλ 2006 04:30

Όπως κάθε πετυχημένη γλώσσα, έτσι και η JavaScript έχει τα αμφιλεγόμενα σημεία της, για παράδειγμα η αξία της εντολής with ή η συμπεριφορά του keyword this. Ένα ζήτημα που έχει απασχολήσει κατά καιρούς διάφορους σχολιαστές είναι η προαιρετικότητα του χαρακτήρα τερματισμού εντολής semicolon.

;

Η πράξη δείχνει ότι σχεδόν όλοι οι χαρισματικοί συγγραφείς κώδικα JavaScript προτιμούν να βάζουν semicolons, και δε θα βρείτε σοβαρή JS library χωρίς αυτά. Η θέση του Douglas Crockford - αναγνωρισμένη αυθεντία της γλώσσας και δημιουργός του JSON format - είναι σαφέστατη:
Semicolon insertion was a huge mistake, as was the notation for literal regular expressions. These mistakes have led to programming errors, and called the design of the language as a whole into question.
One of the mistakes in the language is semicolon insertion. This is a technique for making semicolons optional as statement terminators. It is reasonable for IDEs and shell programs to do semicolon insertion. It is not reasonable for the language definition to require compilers to do it. Use semicolons.
Το ίδιο κατηγορηματικός είναι και ο Brendan Eich, ο ικανότατος προγραμματιστής που ολομόναχος πριν 11 χρόνια σχεδίασε και υλοποίησε τη γλώσσα. Αλλά προς την αντίθετη κατεύθυνση:
Requiring a semicolon after each statement when a new line would do, was out of the question. Scripting for most people is about writing short snippets of code, quickly and without fuss.
Προσωπικά περιμένω ακόμα να ακούσω ένα καλό επιχείρημα υπέρ της χρήσης των semicolons. Καλώς ή κακώς οι compilers είναι σε θέση να τα προσθέτουν αυτόματα εκεί που πρέπει, οπότε τι νόημα έχει να τα βάζει κανείς με το χέρι; Τι θα συμβεί για παράδειγμα αν ένας φιλόπονος προγραμματιστής γράψει μία library 5.000 γραμμών, προσθέσει τις 3.000 - 4.000 "απαραίτητες" semicolons, και ξεχάσει να βάλει semicolon σε μία εντολή; Ο κώδικας θα τρέξει χωρίς πρόβλημα και πιθανότατα δε θα μάθει ποτέ για αυτή την παράλειψη. Το ερώτημα είναι, αυτό το semicolon που λείπει καθιστά τον κώδικά του ελαττωματικό; Αν ναι, ποιο είναι το ελάττωμά του; Το μόνο ελάττωμα που μπορώ να σκεφτώ είναι η συντακτική ασυνέπεια. Αλλά αν δεν μπορεί ο compiler να ενημερώσει τον προγραμματιστή για αυτή την παράλλειψη, τότε θα πρέπει να βρεθεί άλλος τρόπος ελέγχου. Ίσως να έπρεπε να φτιαχτεί ένα tool που να κάνει το σχετικό έλεγχο, το οποίο θα τρέχει τακτικά ο φιλόπονος προγραμματιστής για να είναι σίγουρος ότι γράφει σωστό κώδικα. Όλα αυτά είναι ωραία, μόνο που υπάρχει ένας πιο απλός τρόπος για την εξασφάλιση της συντακτικής συνέεπειας. Don't use semicolons!

Όπως και να'χει γούστα είναι αυτά, και ορισμένοι υποστηρίζουν πως τα semicolons κάνουν πιο ευανάγνωστο τον κώδικα. Για να είναι όλοι ευχαριστημένοι, ας φτιάξουμε ένα tool που να μπορεί να προσθέτει και να αφαιρεί τις semicolons κατά βούληση. Η ίδια η γλώσσα μας παρέχει έναν αναπάντεχο τρόπο επίλυσης του προβλήματος, τον constructor Function. Όταν δημιουργούμε ένα νέο αντικείμενο function με χρήση του constructor Function, η JavaScript κάνει συντακτικό έλεγχο του πηγαίου κώδικα, χωρίς να τον εκτελεί. Π.χ. Ο παρακάτω κώδικας προκαλεί σφάλμα:

Κώδικας: Επιλογή όλων

new Function('a = 5 return b') //Expected ';'


...ενώ αυτός όχι:

Κώδικας: Επιλογή όλων

new Function('a = 5;return b')
...ανεξάρτητα που η νεοδημιουργηθείσα ρουτίνα δεν τρέχει:

Κώδικας: Επιλογή όλων

new Function('a = 5;return b')() //b is undefined
Επομένως με αυτό τον τρόπο μπορούμε να ελέγξουμε τη συντακτική ορθότητα ενός τμήματος κώδικα, ανεξάρτητα από τη run-time συμπεριφορά του. Μπορούμε λοιπόν να κάνουμε το εξής, να ενώνουμε μία-μία τις γραμμές του πηγαίου κώδικα, και κάθε φορά που θα βγαίνει σφάλμα να βάζουμε ένα semicolon:

Κώδικας: Επιλογή όλων

function insertSemicolons(source) {
  var lines = source.split('\n')
  for &#40;var i = 0; i < lines.length; i++&#41; &#123;
    try &#123;
      new Function&#40;lines.slice&#40;0, i&#41;.join&#40;' '&#41; + ' ' + lines.slice&#40;i, lines.length&#41;.join&#40;'\n'&#41;&#41;
    &#125; catch&#40;e&#41; &#123;
      if &#40;i == 0&#41; throw e
      lines&#91;i - 1&#93; += ';'
    &#125;
  &#125;
  source = lines.join&#40;'\n'&#41;
&#125;
Δυστυχώς αυτή η απλή προσέγγιση δε δουλεύει. Αν υπάρχει κενή γραμμή, το semicolon μπαίνει εκτός θέσης, αλλά το κυριότερο πρόβλημα παρουσιάζεται με τα single-line comments. Έστω για παράδειγμα αυτός ο κώδικας:

Κώδικας: Επιλογή όλων

a = 5 // κάποιο σχόλιο
return b
Κανονικά θα έπρεπε να προστεθεί ένα semicolon πριν από το σχόλιο, αλλά η ρουτίνα insertSemicolons δε θα το προσθέσει. Αυτό θα συμβεί γιατί ο παρακάτω κώδικας είναι συντακτικά άψογος:

Κώδικας: Επιλογή όλων

a = 5 // κάποιο σχόλιο return b
Επομένως για να κάνουμε το script να δουλέψει σωστά, θα πρέπει πρώτα να απαλλαγούμε από τα σχόλια στον κώδικα (αλλά και από τις κενές γραμμές). Όμως αυτό κάθε άλλο παρά εύκολο είναι, γιατί στη JavaScript τα comments δε μπορούν να ξεχωρίσουν από τα strings και τα RegExp literals παρά μόνο με parsing γράμμα-γράμμα. Με αρκετή προσπάθεια κατέληξα στο παρακάτω script:

Κώδικας: Επιλογή όλων

// removeSemicolons&#40;&#41;
function removeSemicolons&#40;source&#41; &#123;
  // Pack comments
  var comments = &#91;&#93;
  source = parseJS&#40;source, function&#40;token, type&#41;&#123;
    if &#40;type == 'multiline comment' || type == 'single-line comment'&#41; &#123;
      comments.push&#40;token&#41;
      return '\x0b'
    &#125; else &#123;
      return token
    &#125;
  &#125;&#41;
  // Remove semicolons
  source = source.replace&#40;/^&#40;\s*&#41;;&#91;;\s&#93;*|&#91;;\s&#93;*;&#40;\s*&#41;$/gm, '$1$2'&#41;
  // Unpack comments
  comments.reverse&#40;&#41;
  return source.replace&#40;/\x0b/g, function&#40;&#41; &#123;
    return comments.pop&#40;&#41;
  &#125;&#41;
&#125;

// insertSemicolons&#40;&#41;
function insertSemicolons&#40;source&#41; &#123;
  // Pack comments
  var comments = &#91;&#93;
  source = parseJS&#40;source, function&#40;token, type&#41; &#123;
    if &#40;type == 'multiline comment' || type == 'single-line comment'&#41; &#123;
      comments.push&#40;token&#41;
      return '\x0b'
    &#125; else &#123;
      return token
    &#125;
  &#125;&#41;
  // Pack seperators
  var seperators = &#91;&#93;
  source = source.replace&#40;/\s*&#91;\r\n&#93;\s*|\s+$/g, function&#40;$0&#41; &#123;
    seperators.push&#40;$0&#41;
    return '\n'
  &#125;&#41;
  // Insert semicolons
  var lines = source.split&#40;'\n'&#41;
  for &#40;var i = 0; i < lines.length; i++&#41; &#123;
    try &#123;
      new Function&#40;lines.slice&#40;0, i&#41;.join&#40;' '&#41; + ' ' + lines.slice&#40;i, lines.length&#41;.join&#40;'\n'&#41;&#41;
    &#125; catch&#40;e&#41; &#123;
      if &#40;i == 0&#41; throw e
      lines&#91;i - 1&#93; += ';'
    &#125;
  &#125;
  source = lines.join&#40;'\n'&#41;
  // Unpack seperators
  seperators.reverse&#40;&#41;
  source = source.replace&#40;/\n/g, function&#40;&#41; &#123;
    return seperators.pop&#40;&#41;
  &#125;&#41;
  // Unpack comments
  comments.reverse&#40;&#41;
  return source.replace&#40;/\x0b/g, function&#40;&#41; &#123;
    return comments.pop&#40;&#41;
  &#125;&#41;
&#125;

// parseJS&#40;&#41;
function parseJS&#40;js, callback&#41; &#123;
  // Remove control characters except \t, \n, \r.
  js = js.replace&#40;/&#91;\x00-\x08\x0b\x0c\x0e-\x1f&#93;/g, ''&#41;
  var nodes = &#91;&#93;
  var expressions = &#91;
    /&#40;&#40;&#91;"'&#93;&#41;&#40;\\.|&#91;^\2\\&#93;&#41;*?\2&#41;/.source,                                         // String.
    /&#40;\/\*&#91;\s\S&#93;*?\*\/&#41;/.source,                                                // Multiline Comment.
    /&#40;\/\/&#91;^\r\n&#93;*&#41;/.source,                                                    // Single-line Comment.
    /&#40;&#40;^|&#91;^\w\s\&#41;\&#93;\&#125;&#93;&#41;\s*&#41;&#40;\/&#40;\\.|&#40;\&#91;&#40;\\.|&#91;^\&#93;&#93;&#41;*\&#93;&#41;|&#91;^\/&#93;&#41;+\/&#91;gmi&#93;*&#41;/.source  // Regular Expression.
  &#93;
  var reComplex = new RegExp&#40;expressions.join&#40;'|'&#41;, 'g'&#41;
  return js.replace&#40;reComplex, function&#40;$0, $1, $2, $3, $4, $5, $6, $7, $8&#41; &#123;
    return &#40;$1 && callback&#40;$1, 'string'&#41;&#41; ||
           &#40;$4 && callback&#40;$4, 'multiline comment'&#41;&#41; ||
           &#40;$5 && callback&#40;$5, 'single-line comment'&#41;&#41; ||
           &#40;$8 && $6 + callback&#40;$8, 'regular expression'&#41;&#41; || $0
  &#125;&#41;
&#125;
Πού θα βρούμε τώρα λίγο κώδικα JS για να δοκιμάσουμε αν δουλεύει το script; Χμ, τι θα λέγατε να το δοκιμάζαμε στον εαυτό του;

Κώδικας: Επιλογή όλων

// removeSemicolons&#40;&#41;
function removeSemicolons&#40;source&#41; &#123;
  // Pack comments
  var comments = &#91;&#93;;
  source = parseJS&#40;source, function&#40;token, type&#41;&#123;
    if &#40;type == 'multiline comment' || type == 'single-line comment'&#41; &#123;
      comments.push&#40;token&#41;;
      return '\x0b'
    &#125; else &#123;
      return token
    &#125;
  &#125;&#41;;
  // Remove semicolons
  source = source.replace&#40;/^&#40;\s*&#41;;&#91;;\s&#93;*|&#91;;\s&#93;*;&#40;\s*&#41;$/gm, '$1$2'&#41;;
  // Unpack comments
  comments.reverse&#40;&#41;;
  return source.replace&#40;/\x0b/g, function&#40;&#41; &#123;
    return comments.pop&#40;&#41;
  &#125;&#41;
&#125;

// insertSemicolons&#40;&#41;
function insertSemicolons&#40;source&#41; &#123;
  // Pack comments
  var comments = &#91;&#93;;
  source = parseJS&#40;source, function&#40;token, type&#41; &#123;
    if &#40;type == 'multiline comment' || type == 'single-line comment'&#41; &#123;
      comments.push&#40;token&#41;;
      return '\x0b'
    &#125; else &#123;
      return token
    &#125;
  &#125;&#41;;
  // Pack seperators
  var seperators = &#91;&#93;;
  source = source.replace&#40;/\s*&#91;\r\n&#93;\s*|\s+$/g, function&#40;$0&#41; &#123;
    seperators.push&#40;$0&#41;;
    return '\n'
  &#125;&#41;;
  // Insert semicolons
  var lines = source.split&#40;'\n'&#41;;
  for &#40;var i = 0; i < lines.length; i++&#41; &#123;
    try &#123;
      new Function&#40;lines.slice&#40;0, i&#41;.join&#40;' '&#41; + ' ' + lines.slice&#40;i, lines.length&#41;.join&#40;'\n'&#41;&#41;
    &#125; catch&#40;e&#41; &#123;
      if &#40;i == 0&#41; throw e;
      lines&#91;i - 1&#93; += ';'
    &#125;
  &#125;
  source = lines.join&#40;'\n'&#41;;
  // Unpack seperators
  seperators.reverse&#40;&#41;;
  source = source.replace&#40;/\n/g, function&#40;&#41; &#123;
    return seperators.pop&#40;&#41;
  &#125;&#41;;
  // Unpack comments
  comments.reverse&#40;&#41;;
  return source.replace&#40;/\x0b/g, function&#40;&#41; &#123;
    return comments.pop&#40;&#41;
  &#125;&#41;
&#125;

// parseJS&#40;&#41;
function parseJS&#40;js, callback&#41; &#123;
  // Remove control characters except \t, \n, \r.
  js = js.replace&#40;/&#91;\x00-\x08\x0b\x0c\x0e-\x1f&#93;/g, ''&#41;;
  var nodes = &#91;&#93;;
  var expressions = &#91;
    /&#40;&#40;&#91;"'&#93;&#41;&#40;\\.|&#91;^\2\\&#93;&#41;*?\2&#41;/.source,                                         // String.
    /&#40;\/\*&#91;\s\S&#93;*?\*\/&#41;/.source,                                                // Multiline Comment.
    /&#40;\/\/&#91;^\r\n&#93;*&#41;/.source,                                                    // Single-line Comment.
    /&#40;&#40;^|&#91;^\w\s\&#41;\&#93;\&#125;&#93;&#41;\s*&#41;&#40;\/&#40;\\.|&#40;\&#91;&#40;\\.|&#91;^\&#93;&#93;&#41;*\&#93;&#41;|&#91;^\/&#93;&#41;+\/&#91;gmi&#93;*&#41;/.source  // Regular Expression.
  &#93;;
  var reComplex = new RegExp&#40;expressions.join&#40;'|'&#41;, 'g'&#41;;
  return js.replace&#40;reComplex, function&#40;$0, $1, $2, $3, $4, $5, $6, $7, $8&#41; &#123;
    return &#40;$1 && callback&#40;$1, 'string'&#41;&#41; ||
           &#40;$4 && callback&#40;$4, 'multiline comment'&#41;&#41; ||
           &#40;$5 && callback&#40;$5, 'single-line comment'&#41;&#41; ||
           &#40;$8 && $6 + callback&#40;$8, 'regular expression'&#41;&#41; || $0
  &#125;&#41;
&#125;
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.

Άβαταρ μέλους
skeftomilos
Script Master
Δημοσιεύσεις: 2888
Εγγραφή: 07 Ιαν 2005 07:22
Τοποθεσία: Αθήνα

Semicolons, αφαίρεση και προσθήκη

Δημοσίευση από skeftomilos » 18 Αύγ 2006 01:36

Βρέθηκε τρύπα στον κώδικα, συγκεκριμένα στην περίπτωση γραμμών που τελειώνουν με return. Π.χ.:

Κώδικας: Επιλογή όλων

if &#40;condition&#41; return
otherStuff&#40;&#41;
Κανονικά εδώ πρέπει να μπει semicolon, αλλά δε μπαίνει γιατί μονοκόμματο είναι έγκυρη εντολή JavaScript:

Κώδικας: Επιλογή όλων

if &#40;condition&#41; return otherStuff&#40;&#41;
Μια λύση είναι να προστεθεί η παρακάτω εντολή κάτω από το σχόλιο // Insert semicolons

Κώδικας: Επιλογή όλων

// Insert semicolons
source = source.replace&#40;/\b&#40;return&#41;&#40;\s*\n\s*&#41;/g, '$1;$2'&#41;
...
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.

Άβαταρ μέλους
JavaS
Δημοσιεύσεις: 103
Εγγραφή: 14 Ιαν 2004 13:10
Τοποθεσία: Βόλος

Semicolons, αφαίρεση και προσθήκη

Δημοσίευση από JavaS » 24 Αύγ 2006 12:47

Αν και είναι αρκετά ενδιαφέρον το όλο ιστορικό σχετικά με τη διαμάχη semicolons or not (δεν το ήξερα ότι υπάρχει τέτοια αντιπαράθεση), ειλικρινά δεν μπορώ να καταλάβω το λόγο για τον οποίο υπάρχει.
Από όσο κατάλαβα η μία πλευρά βαριέται να βάζει semicolons (αφού μπορεί να τα βάλει ο compiler μόνος του), ενώ η άλλη "γουστάρει" χωρίς να έχω καταλάβει γιατί.

Ανήκω στη κατηγορία που καραγουστάρει να βάζει semicolons, για τον απλό λόγο ότι με βολεύει στο debugging και ... γιατί κάνει τη διαφορά! :-) Άλλωστε και στη C# τα semicolons είναι απαραίτητα και έχω συνηθίσει.
en telh, PHP kai agios o theos.. ASP kai agia MS :P dialegeis kai perneis :P

Άβαταρ μέλους
cherouvim
Script Master
Δημοσιεύσεις: 3137
Εγγραφή: 13 Ιούλ 2005 22:56
Τοποθεσία: Athens, Greece
Επικοινωνία:

Semicolons, αφαίρεση και προσθήκη

Δημοσίευση από cherouvim » 24 Αύγ 2006 16:12

Σε πιες άλλες γλώσσες, εκτός της Javascript, δεν είναι απαραίτητα τα semicolons;

Άβαταρ μέλους
skeftomilos
Script Master
Δημοσιεύσεις: 2888
Εγγραφή: 07 Ιαν 2005 07:22
Τοποθεσία: Αθήνα

Semicolons, αφαίρεση και προσθήκη

Δημοσίευση από skeftomilos » 25 Αύγ 2006 01:25

Απ' ότι φαίνεται είναι κάμποσες οι παραλλαγές στο θέμα. Υπάρχουν γλώσσες που δε χρησιμοποιούνται καθόλου semicolons, όπως η Basic, η Fortran, η COBOL, η Ruby, η Python, η Haskell, η TSQL και άλλες. Υπάρχουν άλλες που τα semicolons είναι υποχρεωτικά ως statement terminators, π.χ. C, C#, Java, PHP και άλλες. Στην Pascal τα semicolons είναι statement separators, το οποίο είναι σχεδόν το ίδιο με τη διαφορά ότι στην τελευταία εντολή ενός block BEGIN...END το semicolon είναι περιττό. Γλώσσες εκτός από τη JavaScript με προαιρετικά όλα τα semicolons είναι η Eiffel και η TCL. Τέλος υπάρχουν και γλώσσες που έχουν κάποιο άλλο χαρακτήρα για να χωρίζουν τις εντολές αντί για semicolon ή new-line. Η Prolog και η Smalltalk χρησιμοποιούν τελεία, ενώ η O-Caml διπλό semicolon ;;

Τα παραπάνω με κάθε επιφύλαξη, γιατί προέκυψαν από μια σύντομη περιήγηση στη Wikipedia. :)

Κάπου διάβασα μια στατιστική ότι το 20% των σφαλμάτων που γίνονται από προγραμματιστές στις semicolon-terminated γλώσσες αφορούν παράλειψη πληκτρολόγησης του semicolon. Από την καθημερινή προσωπική μου επαφή με τη C# το ποσοστό φαίνεται μετριοπαθές. Από την άλλη λέγεται ότι αυτά τα σφάλματα εντοπίζονται και διορθώνονται εύκολα, ενώ η προαιρετικότητα των semicolons μπορεί να οδηγήσει σε πολύ πιο δυσεπίλυτα bugs. Στη JavaScript όταν ο κώδικας αρχίζει να γίνεται περίπλοκος το debugging γίνεται black-art, αλλά έχω την εντύπωση πως δε φταίνε τα semicolons γι αυτό.
The pure and simple truth is rarely pure and never simple. Ο μη νους δε σκέπτεται μη σκέψεις για το τίποτα.

Άβαταρ μέλους
JavaS
Δημοσιεύσεις: 103
Εγγραφή: 14 Ιαν 2004 13:10
Τοποθεσία: Βόλος

Semicolons, αφαίρεση και προσθήκη

Δημοσίευση από JavaS » 25 Αύγ 2006 16:27

skeftomilos έγραψε:Κάπου διάβασα μια στατιστική ότι το 20% των σφαλμάτων που γίνονται από προγραμματιστές στις semicolon-terminated γλώσσες αφορούν παράλειψη πληκτρολόγησης του semicolon. Από την καθημερινή προσωπική μου επαφή με τη C# το ποσοστό φαίνεται μετριοπαθές. Από την άλλη λέγεται ότι αυτά τα σφάλματα εντοπίζονται και διορθώνονται εύκολα, ενώ η προαιρετικότητα των semicolons μπορεί να οδηγήσει σε πολύ πιο δυσεπίλυτα bugs. Στη JavaScript όταν ο κώδικας αρχίζει να γίνεται περίπλοκος το debugging γίνεται black-art, αλλά έχω την εντύπωση πως δε φταίνε τα semicolons γι αυτό.
Θα συμφωνήσω απόλυτα, κρίνοντας από τη προσωπική μου εμπειρία το ποσοστό των σφαλμάτων που οφείλονται στην έλλειψη των semicolons είναι μεγαλύτερο. Ωστόσο, είναι πράγματι ευκολότερο να βρείς το λάθος.

Με απλά λόγια, θεωρώ ότι η ύπαρξη των semicolons ή εν πάσει περιπτώση οποιουδήποτε termination (εκτός του CrLf) είναι προτέρημα για τον προγραμματιστή.
en telh, PHP kai agios o theos.. ASP kai agia MS :P dialegeis kai perneis :P

Απάντηση

Επιστροφή στο “JavaScript και Frameworks”

Μέλη σε σύνδεση

Μέλη σε αυτήν τη Δ. Συζήτηση: Δεν υπάρχουν εγγεγραμμένα μέλη και 0 επισκέπτες