Depuração

Em scripts, nenhuma função é infalível. Mesmo os comandos mais confiáveis podem falhar quando confrontados com entradas ou condições inesperadas. Para nos proteger contra isso, podemos implementar um sistema de depuração personalizado e adotar técnicas de programação defensivas. Ao agrupar funções padrão com mecanismos de tratamento de erros e fornecer feedback informativo, podemos tornar nossos scripts mais robustos e fáceis de solucionar.

Uma parte importante desta estratégia é usar um sinalizador de depuração global para controlar a saída detalhada, permitindo-nos ativar informações detalhadas de depuração quando necessário, mantendo a saída limpa durante a execução normal.

Sinalizador de depuração global

Um sinalizador de depuração global é uma maneira simples, mas eficaz, de controlar o nível de saída de informações durante a execução do script. Quando ativado, ele fornece mensagens de depuração detalhadas que podem ser inestimáveis para rastrear problemas. Quando desabilitado, mantém a saída concisa para uso em produção.

;; Propósito: Flag global para controlar a saída de depuração.
(define debug #f)

Por padrão, a depuração está desativada. Para ativar a saída detalhada durante o desenvolvimento, basta definir o sinalizador como #t:

;; Propósito: Flag global para controlar a saída de depuração.
(define debug #t)

Também podemos ativar ou desativar temporariamente a depuração para seções específicas do código usando funções auxiliares.

Controle de depuração local

Para um controle mais preciso, podemos ativar ou desativar a depuração em partes específicas do script usando funções auxiliares.

;; Propósito: Desativar o modo de depuração para uma seção de código.
(define (debug-off)
  (set! debug #f))

;; Propósito: Ativar o modo de depuração para uma seção de código.
(define (debug-on)
  (set! debug #t))

Isso nos permite controlar a depuração dinamicamente:

(debug-on)  ;; Ativar saída detalhada

;; Alguma lógica de script aqui

(debug-off) ;; Desativar saída detalhada

Depurar sistema de mensagens

Para lidar com eficiência com a saída de depuração em Scheme, usamos uma abordagem estruturada envolvendo várias funções auxiliares. Essas funções garantem que as mensagens de depuração e aviso sejam claras, legíveis e fáceis de manter.

Visão geral do sistema de mensagens de depuração

Nosso sistema de mensagens de depuração consiste nos seguintes componentes:

  1. debug-message – Exibe mensagens de depuração quando a depuração está habilitada.
  2. serialize-item – Converte vários tipos de dados de Scheme em uma representação de string.
  3. concat – Concatena vários itens em uma única string.
  4. list->string – Formata uma lista em uma string legível.
  5. message – Exibe a saída no console de mensagens do Lumi.
  6. warning-message – Exibe mensagens de aviso quando os avisos estão habilitados.

Cada função desempenha uma função na formatação e exibição de mensagens estruturadas.


Função de mensagem de depuração

A função debug-message é o método principal para exibir a saída de depuração. Ele garante que as mensagens sejam mostradas apenas quando a depuração estiver habilitada.

;; Propósito: Exibir uma mensagem de depuração.
(define (debug-message . items)
  (when debug (message "> " (apply concat items))))
  • A condição when debug garante que as mensagens apareçam apenas quando a depuração estiver habilitada.
  • As mensagens são prefixadas com "> " para maior clareza.
  • A função usa concat para formatar o conteúdo da mensagem.
  • Por fim, chama message para enviar a saída para o console de mensagens do Lumi.

Exemplo de uso:

;; Propósito: Retorna a posição na árvore do item ou #f se o item for inválido
(define (get-item-tree-position image item)
  (if (item-is-valid? item)
    (let ((position (list->item (lumi-image-get-item-position image item))))
      (debug-message "item : " (item-get-name item) " has tree position : " position)
      position)
    #f))

Com a depuração habilitada, a saída pode ser:

> item: background-layer has tree position : 3

Serializando dados para mensagens de depuração

As mensagens podem conter diferentes tipos de dados, como listas, vetores e números. Para garantir que estejam formatados corretamente, usamos serialize-item.

;; Propósito: Converte vários tipos de dados Scheme (listas, vetores, pares, etc.)
;;          em uma representação de string.
(define (serialize-item item)
  (cond
    ((and (list? item) (null? item)) "\"\"")          ; Lista vazia
    ((and (string? item) (string=? item "")) "\"\"")  ; String vazia
    ((list? item) (list->string item))                ; Lista aninhada
    ((vector? item)                                   ; Trata vetores
     (string-append "#("
                    (string-join (map serialize-item (vector->list item)) " ")
                    ")"))
    ((pair? item)                                     ; Trata pares
     (string-append "("
                    (serialize-item (car item))
                    " . "
                    (serialize-item (cdr item))
                    ")"))
    ((number? item) (number->string item))            ; Números
    ((symbol? item) (symbol->string item))            ; Símbolos
    ((boolean? item) (if item "#t" "#f"))             ; Booleanos
    ((string? item) item)                             ; Cadeias de caracteres
    (else (warning-message "serialize-item: Unsupported item type!" item))))

Exemplo de uso:

(serialize-item '(1 2 3))

Saída:

list:
1
2
3

Concatenação para Mensagens

Para mesclar vários componentes de mensagem em uma única string, usamos concat.

;; Propósito: Concatenar vários itens em uma única string.
(define (concat . items)
  (apply string-append (map serialize-item items)))

Exemplo de uso:

(concat "Image size: " 1920 "x" 1080)

Formatando listas como strings

A função list->string converte uma lista em uma string formatada.

;; Propósito: Converter uma lista de itens em uma string legível.
(define (list->string list)
  (if (list? list)
      (string-append "list: \n" (string-join (map serialize-item list) "\n"))
      (warning-message "list->string: Input is not a list!")))

Mensagens de aviso

A função warning-message funciona de forma semelhante a debug-message, mas exibe avisos mesmo quando a depuração está desabilitada.

;; Propósito: Exibir uma mensagem de aviso.
(define (warning-message . items)
  (if warning
    (message "Warning: " (apply concat items)))
    #f)
  • Garante que as mensagens sejam mostradas apenas quando os avisos estão habilitados (o sinalizador warning é definido em common.scm como #t).
  • Chama concat para formatar o conteúdo da mensagem.
  • Usa message para enviar saída para Lumi.

Aprimorando funções padrão

Uma vez implementado um sistema de depuração, podemos aprimorar nossa biblioteca de funções incorporando mensagens detalhadas. Isso fornece informações sobre estados de itens, valores de variáveis e chamadas de função.

Um exemplo comum é item-is-valid?, que envolve lumi-item-id-is-valid para retornar #t ou #f. Se #f for retornado, podemos acionar um warning-message no código de chamada, se a entrada não for um número podemos dar um aviso na função.

;; Propósito: Verificar se um item é válido, retorna #t ou #f.
;;          Emite um aviso se o item não for um número.
(define (item-is-valid? item)
  (if (number? item)
      (= (list->item (lumi-item-id-is-valid item)) 1)
      (begin
        (warning-message "item-is-valid?: Expected a number, but received: " item)
        #f)))

Uso prático

Ao desenvolver plug-ins de Scheme, agrupar funções dessa maneira reduz significativamente o tempo de depuração e garante um código robusto e de fácil manutenção. Com nosso sistema de depuração instalado, podemos gerar um fluxo de depuração estruturado no console de erros com o toque de um botão.

Neste fluxo de depuração, as chamadas de função são marcadas com um asterisco (*), facilitando o rastreamento da execução de scripts e a identificação de falhas, especialmente em plug-ins complexos. Essa visibilidade nos ajuda a entender o fluxo das operações e a diagnosticar comportamentos inesperados com eficiência.

Um wrapper para nossa função de mensagem usar um *

(define (call . items)
  (when debug (message "* (" (apply concat items) ")")))

Exemplo de call sendo usado na prática:

;; Propósito: Aplica o processo de texturização à lista fornecida de máscaras de grupo
(define (process-masks groups pattern) (call 'process-masks)
  (for-each
    (lambda (group)
      (let ((mask (add-mask-to-layer group ADD-MASK-WHITE)))
        (message "Process mask : " (item-get-name group))
        (fill-and-adjust-group-mask group mask pattern)
        (lumi-layer-set-opacity group (get 'color-opacity))
        (lumi-item-set-expanded (item-get-parent group) 0)
        (lumi-selection-none (get-image))))
    (ensure-list groups)))

Exemplo de um fluxo de depuração conforme um plug-in é executado:

> Recording the plug-in settings
* (convert-gui-settings)
> all-masks : 1
> strokes : 1
> color : 1
> plate-layer : 1
> drawables : #(37)
* (filter-list-for-matching-groups)
> all-masks : #t
> sub-groups of group : root
blue
blue_strokes
blue_colour
yellow
yellow_strokes
yellow_colour
gray
gray_strokes
gray_colour
> groups with identifier in name: _colour
blue_colour
yellow_colour
gray_colour
* (filter-list-for-matching-groups)
> all-masks : #t
> sub-groups of group : root
blue
blue_strokes
blue_colour
yellow
yellow_strokes
yellow_colour
gray
gray_strokes
gray_colour
> groups with identifier in name: _strokes
blue_strokes
yellow_strokes
gray_strokes
* (begin-apply-texture)

Start Apply Texture

> color : #t

Texturing color group masks
> color-pattern : 2655
* (process-masks)
Process mask : blue_colour
* (fill-and-adjust-group-mask)
> Fill-and-adjust : blue_colour mask
> using pattern for fill : 2655
* (apply-color-effect)
> color-contrast : 64
> color-levels-gamma : 10
> levels on drawable: blue_colour mask
>   gamma: 8.2
>   low-in: 0.7278  high-in: 0.9222
>   low-out: 0  high-out: 1
> light-opacity : 6
> light-opacity : 6
* (apply-light-effect)
> apply-light-effect opacity : 6
> from layer : light_blue
> edit-copy light_blue
> edit-paste blue_colour mask
> shade-opacity : 60
> shade-opacity : 60
* (apply-light-effect)
> apply-light-effect opacity : 60
> from layer : shad_blue_opa*5
> edit-copy shad_blue_opa*5
> edit-paste blue_colour mask
* (apply-opaque-effect)
> children in : blue_colour
blue_colour
hue_blue
light_blue
shad_blue_opa*5
base_blue
...
...
...
Finished Apply Texture!

Esse log estruturado fornece um cronograma claro de chamadas de função e alterações de dados, facilitando significativamente a depuração e a análise de desempenho.

Conclusão

Ao implementar um sistema de depuração estruturado, criamos scripts mais seguros e de fácil manutenção que oferecem insights em tempo real sobre sua execução.

Principais conclusões

  • Controlar verbosidade – Use um sinalizador de depuração global para gerenciar os níveis de saída.
  • Forneça feedback claro – Envolva funções padrão com mensagens informativas de depuração.
  • Aumente a robustez – Lide com entradas inesperadas com elegância para evitar erros.
  • Simplifique a solução de problemas – Mensagens de depuração estruturadas facilitam o diagnóstico e a correção de problemas.

Com essa abordagem, nossos scripts “explicam-se” efetivamente à medida que processam dados, reduzindo a frustração e melhorando a eficiência do fluxo de trabalho. A depuração se torna uma ferramenta proativa em vez de uma tarefa reativa, tornando nosso processo de script mais suave e gratificante.