Perché le funzioni di Emac sono così grandi? [chiuso]

0

Sono un principiante in ELisp , ma ho programmato in C++ e in un certo numero di altri linguaggi di programmazione. La mia regola empirica (e penso che sia comune) che una funzione dovrebbe adattarsi allo schermo. Tuttavia, dovrebbe preferibilmente essere ancora più piccolo (20 linee sono sul lato lungo). Tuttavia, noto che Emacs ha funzioni molto lunghe, con molte opportunità di decomposizione. Un esempio di ciò che intendo segue.

La domanda è: c'è qualcosa che mi manca per quanto riguarda la differenza tra la programmazione in ELisp e la programmazione in linguaggi procedurali? Quelli che hanno scritto i programmatori di Emacs sono cattivi? (difficile da credere) Altri motivi?

 (defun forward-paragraph (&optional arg)
  "Move forward to end of paragraph.
With argument ARG, do it ARG times;
a negative argument ARG = -N means move backward N paragraphs.

A line which 'paragraph-start' matches either separates paragraphs
\(if 'paragraph-separate' matches it also) or is the first line of a paragraph.
A paragraph end is the beginning of a line which is not part of the paragraph
to which the end of the previous line belongs, or the end of the buffer.
Returns the count of paragraphs left to move."
  (interactive "^p")
  (or arg (setq arg 1))
  (let* ((opoint (point))
     (fill-prefix-regexp
      (and fill-prefix (not (equal fill-prefix ""))
           (not paragraph-ignore-fill-prefix)
           (regexp-quote fill-prefix)))
     ;; Remove ^ from paragraph-start and paragraph-sep if they are there.
     ;; These regexps shouldn't be anchored, because we look for them
     ;; starting at the left-margin.  This allows paragraph commands to
     ;; work normally with indented text.
     ;; This hack will not find problem cases like "whatever\|^something".
     (parstart (if (and (not (equal "" paragraph-start))
                (equal ?^ (aref paragraph-start 0)))
               (substring paragraph-start 1)
             paragraph-start))
     (parsep (if (and (not (equal "" paragraph-separate))
              (equal ?^ (aref paragraph-separate 0)))
             (substring paragraph-separate 1)
           paragraph-separate))
     (parsep
      (if fill-prefix-regexp
          (concat parsep "\|"
              fill-prefix-regexp "[ \t]*$")
        parsep))
     ;; This is used for searching.
     (sp-parstart (concat "^[ \t]*\(?:" parstart "\|" parsep "\)"))
     start found-start)
    (while (and (< arg 0) (not (bobp)))
      (if (and (not (looking-at parsep))
           (re-search-backward "^\n" (max (1- (point)) (point-min)) t)
           (looking-at parsep))
      (setq arg (1+ arg))
    (setq start (point))
    ;; Move back over paragraph-separating lines.
    (forward-char -1) (beginning-of-line)
    (while (and (not (bobp))
            (progn (move-to-left-margin)
               (looking-at parsep)))
      (forward-line -1))
    (if (bobp)
        nil
      (setq arg (1+ arg))
      ;; Go to end of the previous (non-separating) line.
      (end-of-line)
      ;; Search back for line that starts or separates paragraphs.
      (if (if fill-prefix-regexp
          ;; There is a fill prefix; it overrides parstart.
          (let (multiple-lines)
            (while (and (progn (beginning-of-line) (not (bobp)))
                (progn (move-to-left-margin)
                       (not (looking-at parsep)))
                (looking-at fill-prefix-regexp))
              (unless (= (point) start)
            (setq multiple-lines t))
              (forward-line -1))
            (move-to-left-margin)
            ;; This deleted code caused a long hanging-indent line
            ;; not to be filled together with the following lines.
            ;; ;; Don't move back over a line before the paragraph
            ;; ;; which doesn't start with fill-prefix
            ;; ;; unless that is the only line we've moved over.
            ;; (and (not (looking-at fill-prefix-regexp))
            ;;      multiple-lines
            ;;      (forward-line 1))
            (not (bobp)))
        (while (and (re-search-backward sp-parstart nil 1)
                (setq found-start t)
                ;; Found a candidate, but need to check if it is a
                ;; REAL parstart.
                (progn (setq start (point))
                   (move-to-left-margin)
                   (not (looking-at parsep)))
                (not (and (looking-at parstart)
                      (or (not use-hard-newlines)
                      (bobp)
                      (get-text-property
                       (1- start) 'hard)))))
          (setq found-start nil)
          (goto-char start))
        found-start)
          ;; Found one.
          (progn
        ;; Move forward over paragraph separators.
        ;; We know this cannot reach the place we started
        ;; because we know we moved back over a non-separator.
        (while (and (not (eobp))
                (progn (move-to-left-margin)
                   (looking-at parsep)))
          (forward-line 1))
        ;; If line before paragraph is just margin, back up to there.
        (end-of-line 0)
        (if (> (current-column) (current-left-margin))
            (forward-char 1)
          (skip-chars-backward " \t")
          (if (not (bolp))
              (forward-line 1))))
        ;; No starter or separator line => use buffer beg.
        (goto-char (point-min))))))

    (while (and (> arg 0) (not (eobp)))
      ;; Move forward over separator lines...
      (while (and (not (eobp))
          (progn (move-to-left-margin) (not (eobp)))
          (looking-at parsep))
    (forward-line 1))
      (unless (eobp) (setq arg (1- arg)))
      ;; ... and one more line.
      (forward-line 1)
      (if fill-prefix-regexp
      ;; There is a fill prefix; it overrides parstart.
      (while (and (not (eobp))
              (progn (move-to-left-margin) (not (eobp)))
              (not (looking-at parsep))
              (looking-at fill-prefix-regexp))
        (forward-line 1))
    (while (and (re-search-forward sp-parstart nil 1)
            (progn (setq start (match-beginning 0))
               (goto-char start)
               (not (eobp)))
            (progn (move-to-left-margin)
               (not (looking-at parsep)))
            (or (not (looking-at parstart))
            (and use-hard-newlines
                 (not (get-text-property (1- start) 'hard)))))
      (forward-char 1))
    (if (< (point) (point-max))
        (goto-char start))))
    (constrain-to-field nil opoint t)
    ;; Return the number of steps that could not be done.
    arg))
    
posta AlwaysLearning 19.05.2017 - 13:34
fonte

1 risposta

3

La lunghezza dei blocchi di codice nominati potrebbe essere una buona metrica. Sappiamo che a più righe di codice, più potenziali errori ※ 1 . Questo è facilmente inteso dal fatto che ogni linea di codice rappresenta un possibile punto di errore.

In effetti, i bug per linee di codice (per una squadra particolare) sono una metrica molto stabile ※ 2 . I fattori principali di questa metrica sono l'esperienza degli sviluppatori e la selezione degli strumenti ※ 3 . Tuttavia, l'esperienza degli sviluppatori non ha cambiamenti improvvisi (modifiche dirette della squadra) e la selezione dello strumento cambia con bassa frequenza.

Tuttavia, se utilizzi questa metrica, dovresti considerare le soglie ※ 4 indipendentemente per ogni lingua, perché l'espressività delle lingue varia (ad esempio, non sarai in grado di fare altrettanto in 10 righe di Assembla come fai in 10 linee di Python).

Riguardo al miglioramento del codice presentato. Deve essere possibile (supponendo che ci sia margine di miglioramento è una buona euristica). Tuttavia, non sarò in grado di dire quali miglioramenti sarebbero buoni, perché non ho familiarità con Lisp.

Perché non hanno migliorato questo codice?

Potrebbero esserci altre preoccupazioni oltre al numero di righe di codice, come ad esempio:

  • Prestazioni: presupponendo che il refactoring introduca chiamate di funzione, potrebbe avere un impatto notevole sulle prestazioni, a seconda dell'implementazione della lingua. Questo di solito non è un problema, ma potrebbe essere per un software sensibile alle prestazioni (come videogiochi, sistemi in tempo reale e software pensato per funzionare con hardware più vecchio).
  • Leggibilità della cronologia delle versioni: se iniziamo a spostare il codice, renderà più difficile tenere traccia nel controllo della versione. Questo renderà difficile trovare dove hanno introdotto un bug. Se questa è una preoccupazione per il team, scoraggeranno il refactoring che non risolve un bug o aggiunge nuove funzionalità. Un esempio è il progetto Mono , che include nel suo linee guida :

    In general, we do not accept patches that merely shuffle code around, split classes in multiple files, reindent the code or are the result of running a refactoring tool on the source code. (...) it destroys valuable history that is often used to investigate bugs, regressions and problems.

  • Costo opportunità: anziché utilizzare il codice di refactoring per ridurre il numero di righe, è meglio utilizzare il tempo e le risorse del team per migliorare il software. Ciò significa correggere qualcosa o aggiungere qualcosa di nuovo al progetto. Inoltre, quando la squadra non è impegnata a farlo ... beh, sono esseri umani e hanno una vita.

Anche questi, ma non particolarmente in Lisp ※ 5 :

  • Incapsulamento: a seconda della lingua e della piattaforma, potrebbe non essere possibile suddividere il codice in funzione senza esporli. Se ciò può consentire ai clienti (magari tramite riflessione, meccanismi di controllo o altri trucchetti) di lasciare il sistema in uno stato non valido, di farlo smettere di funzionare o di accedere a informazioni riservate, si ha un problema di sicurezza.
  • Inquinamento dello spazio dei nomi: il problema potrebbe non essere quello di rompere la sicurezza della funzione o in modo efficiente, ma la quantità di nuove funzioni che introdurremmo se lo facessimo. Inoltre, hanno bisogno di nomi ragionevoli. Venire con i nomi può essere problematico in se stesso (in particolare se la piattaforma ha una lunghezza massima per gli identificatori, ti guarda su Oracle ¬ ¬ ), a parte questo potrebbe anche influire sulle prestazioni in una versione interpretata della lingua.

※ 1 : El Emam et al (2001): l'effetto confondente della dimensione della classe sulla validità delle metriche orientate agli oggetti

※ 2 : Hatton, Leslie (1995): linguaggi di programmazione informatica e sistemi relativi alla sicurezza

Vedi anche:

※ 3 : il completamento automatico, la generazione del codice e altre funzionalità possono potenzialmente ridurre il numero di bug introdotti nel software per riga di codice.

※ 4 : per soglie intendo: quante linee attiveranno una revisione del codice e quanti ne attiveranno il refactoring.

※ 5 : Lisp ha funzioni anonime; infatti, Lisp era il pioniere in questo. Questi risolvono l'incapsulamento e l'inquinamento dello spazio dei nomi in un'unica operazione. Anche se, ho definito la metrica come "Lunghezza dei blocchi di codice nominati" ... quindi questi problemi si applicano ancora, tipo di.

    
risposta data 19.05.2017 - 17:24
fonte

Leggi altre domande sui tag