29
Formalização de um sistema de sincronização de gestores de informação pessoal Edgar Emanuel Marques Gonçalves 28 de Junho de 2006 Resumo A formalização de um sistema informático confere-lhe um conjunto de propriedades úteis, como a clareza na especificação e a demons- tração formal da sua correcção, servindo assim de catalisador para o bom funcionamento do ciclo de desenvolvimento. Neste documento apresento uma pequena introdução acerca da utilização dos métodos formais. Exponho também a metodologia seguida para formalizar um sistema de sincronização de contactos pessoais. 1 Introdução As metodologias formais para o desenvolvimento de software estão associadas à utilização de linguagens matemáticas em uma ou mais fases do processo de desenvolvimento de um sistema, desde a análise dos requisitos à validação do produto final. Estas linguagens são usadas para descrever especificações formais, na primeira fase do processo de desenvolvimento do sistema, e pela sua utilização e actualização no decorrer do processo. Dos principais objectivos de um método de desenvolvimento formal, destacam-se: a detecção de erros de um sistema ou de deficiências face aos requisitos iniciais; a especificação do comportamento funcional de componentes que figu- ram em problemas não triviais; aumentar a compreensão dos comportamentos verificados em sistemas complexos. 1

Formalização de um sistema de sincronização de gestores de ... · sistema de sincronização de contactos pessoais. 1 Introdução As metodologias formais para o desenvolvimento

  • Upload
    ngodang

  • View
    223

  • Download
    0

Embed Size (px)

Citation preview

Formalização de um sistema de sincronizaçãode gestores de informação pessoal

Edgar Emanuel Marques Gonçalves

28 de Junho de 2006

Resumo

A formalização de um sistema informático confere-lhe um conjuntode propriedades úteis, como a clareza na especificação e a demons-tração formal da sua correcção, servindo assim de catalisador para obom funcionamento do ciclo de desenvolvimento. Neste documentoapresento uma pequena introdução acerca da utilização dos métodosformais. Exponho também a metodologia seguida para formalizar umsistema de sincronização de contactos pessoais.

1 IntroduçãoAs metodologias formais para o desenvolvimento de software estão associadasà utilização de linguagens matemáticas em uma ou mais fases do processo dedesenvolvimento de um sistema, desde a análise dos requisitos à validação doproduto final. Estas linguagens são usadas para descrever especificaçõesformais, na primeira fase do processo de desenvolvimento do sistema, epela sua utilização e actualização no decorrer do processo. Dos principaisobjectivos de um método de desenvolvimento formal, destacam-se:

• a detecção de erros de um sistema ou de deficiências face aos requisitosiniciais;

• a especificação do comportamento funcional de componentes que figu-ram em problemas não triviais;

• aumentar a compreensão dos comportamentos verificados em sistemascomplexos.

1

Este artigo tem como objectivo expor, resumidamente, o papel das espe-cificações formais nas metodologias de desenvolvimento formal de software,concretizando com a apresentação das experiências obtidas na construção daespecificação formal de um sistema de sincronização de contactos pessoais.

Na secção seguinte descreve-se uma especificação formal, aos seus papéisnas várias etapas do processo de desenvolvimento formal de software. Asecção 4 é dedicada à apresentação do sistema de sincronização PimSynce da sua especificação formal. Finalmente, são tecidas conclusões sobre aaplicabilidade do processo de desenvolvimento formal no sistema mencionado,na secção 5.

2 Especificação formalUma especificação formal é o artefacto de maior relevo nas metodologiasformais. São documentos que descrevem um sistema, ou um subconjuntode um sistema, de forma isolada. O seu nível de detalhe é tão reduzidoquanto possível para descrever correctamente o comportamento esperado dosistema. Em particular, devem ser omissos no que respeita aos detalhes deimplementação.

Uma especificação formal permite clarificar os requisitos do cliente, re-correndo a uma linguagem matemática (onde se verifica um desencontro delinguagens, ou impedance mismatch, pois o cliente pode não conseguir in-terpretar nem/ou expressar ideias matematicamente). Outra vantagem é afacilitação da descoberta de erros latentes no sistema, através de processos deverificação da correcção da especificação. Uma outra vantagem que merecealgum relevo é o facto de permitir que os detalhes de implementação sejamconsiderados apenas no tempo devido, não se tomando decisões comprome-tedoras do produto final até serem estritamente requeridas.

Podem existir três tipos de especificações formais, ao longo do processode desenvolvimento de um sistema. Ao processar os requisitos recolhidosna fase inicial do projecto, é criada uma especificação abstracta. Esta érefinada, segundo um processo iterativo (idealmente envolvendo o cliente),produzindo um número variado de especificações intermédias. Quando aespecificação for considerada apta para ser usada como base da implementa-ção do projecto, é considerada uma especificação concreta. No entanto,qualquer destas especificações pode ser revista em qualquer ponto do ciclode produção, se assim se justificar.

2

2.1 Propriedades das especificações formais

As especificações formais obedecem a um conjunto de propriedades ([1]).Uma vez que serve de interacção entre uma especificação inicial (por exem-plo, o conjunto de requisitos iniciais de um cliente) e o produtor do sistema,deve ser dotado de desambiguidade, ou seja, só deve transmitir um sig-nificado, seja para quem for. Outro factor necessário numa especificaçãoformal é a sua consistência, que garante que é possível implementar pelomenos um sistema que satisfaça a especificação em causa. Por último, éimportante notar que uma especificação não é, regularmente, completa.Garante-se assim que há detalhes que são deixados ao critério do implemen-tador. Por outro lado, a ausência de completude favorece o carácter iterativodo processo de desenvolvimento formal, bem como das acções de refinamentocomummente praticadas sobre as especificações iniciais. Pode também serconsiderada como um reflexo da incapacidade de modelar a infinidade decenários existentes na interacção entre um dado sistema e o ambiente ondeeste se enquadra.

2.2 Processo de desenvolvimento formal

Todas as etapas do ciclo de vida de um sistema informático podem benefi-ciar da existência de especificações formais. A lista seguinte foca as principaisutilizações das especificações em cada uma das etapas do processo de desen-volvimento de software ([1]).

• Análise de requisitos — Após ter efectuado a recolha dos requisitosdo cliente, é provável que existam requisitos confusos, pouco defini-dos ou até mesmo contraditórios. Ao passar esta listagem para umaespecificação formal, é possível detectar imediatamente algumas con-tradições. Contudo a grande vantagem é poder consolidar com o clientequais os requisitos reais, ao comparar os requisitos por ele definidos coma sua formalização. Adicionalmente, ajuda a resolver o facto do própriocliente poder não ter noção do que os requisitos dele implicam!

• Desenho do sistema — Durante as etapas de decomposição do sis-tema em sub-módulos, a especificação formal é utilizada para garantirque as interfaces são sempre respeitadas. Por outro lado, sempre quese aprofunda o detalhe de um dado módulo (ou seja, sempre que sefaz um refinamento de uma funcionalidade), garante-se que são sem-pre satisfeitos os níveis mais elevados de abstracção. Para provar estasatisfação da especificação, é necessário verificar um conjunto de pro-priedades, denominadas de proof obligations.

3

• Verificação do sistema — Para haver uma verificação de um sistema,é necessário haver uma especificação formal. No entanto, dado que estaverificação é um processo potencialmente moroso e penoso, só é usualefectuar esta verificação para um sub-conjunto crítico da funcionalidadedo sistema.

• Validação — Existem três tipos de validação de sistemas passíveis deserem levados a cabo com especificações formais. Testes de cami-nhos pretendem provar que todos os estados alcançáveis a partir deum determinado conjunto de execução de operações são válidos. Tes-tes de unidade indicam se um dado módulo se comporta de acordocom a especificação. Testes de integração procedem à utilização deum conjunto de módulos, com o objectivo de testar a sua interacção,que se encontra especificada.

• Documentação — A especificação formal é, por si só, um artefactode documentação do sistema. Em particular, este artefacto regista oque o sistema deve efectuar, sem focar a metodologia usada para essepropósito.

• Análise e avaliação — É comum confirmar se o sistema implemen-tado satisfaz a especificação formal inicial. Neste processo, pode dar-sea conhecer que a especificação inicial não era adequada para o pro-blema em causa, ainda que o sistema final a respeite integralmente.Nesta fase existem por vezes problemas de escalabilidade, quando asespecificações são muito extensas ou, alternativamente, se são muitocomplexas (tornando difícil o processo de avaliação).

2.3 Limitações das especificações formais

Contrariamente ao que a generalidade das pessoas acredita ([2]), os métodosformais não implicam correcção do programa, principalmente porque há de-terminadas provas impossíveis de realizar, mas também porque são cometidasfalhas humanas na realização das provas.

Já foi focado o facto das linguagens matemáticas poderem não ser claraspara alguns clientes, o que traz algumas complicações na análise dos requi-sitos. Mas há limitações maiores associadas à criação das especificações.A linguagem matemática permite definir comportamentos exactos, mas nomundo real existe um factor complexo de modelar matematicamente, que é onão-determinismo. Este torna virtualmente impossível criar modelos perfei-tos da realidade, portanto é sempre necessário modelar um sistema da formamais isolada quanto possível. Para este efeito, é comum considerar-se no

4

modelo o ambiente onde se enquadra esse sistema, incluindo todas as inte-racções entre eles. Contúdo, ao contrário do que é viável fazer num sistema,é impossível constrangir o ambiente real. A solução adoptada consiste emassinalar pressupostos acerca do estado do ambiente para os quais o sistemavai funcionar, negligenciando desta forma uma quantidade indeterminada deestados possíveis de tomarem lugar na interacção do sistema no ambiente.

Até agora foi apresentado o conceito de especificação formal e da suaintegração no desenvolvimento formal. Porém, é necessário integrar a espe-cificação, bem como as metodologias de desenvolvimento a esta associadas,no contexto do desenvolvimento de software, ou seja, numa forma tratávelalgoritmicamente. A próxima secção versa acerca desta problemática.

3 Semânticas e a verificação de especificaçõesformais

Para que se possa tirar proveito de automatismos informáticos há que defi-nir a linguagem usada para escrever a especificação formal, de forma a servalidada. Para tal, é necessário especificar tanto a semântica da linguagem,como a sua sintaxe e a sua notação.

A semântica de uma linguagem ([3]), genericamente, consiste no signi-ficado atribuído aos seus componentes. Uma semântica formal tem comobase um modelo matemático, de forma a descrever todos os comportamentospassíveis de serem descritos pela linguagem. Existem várias filosofias relati-vas a esta área, mas as três principais são (1) a semântica denotacional,onde cada frase da especificação corresponde precisamente a uma frase deoutra linguagem (formal), (2) a semântica operacional, segundo a quala execução do sistema deve estar descrita directamente na especificação e(3) a semântica axiomática, onde para cada frase da especificação é su-portada por um conjunto de axiomas (levando portanto a uma especificaçãoexactamente provada numa dada lógica).

Na prática estes tipos de semânticas são usados para objectivos distin-tos. Para se desenhar uma linguagem, interessa fazer um paralelo próximoda especificação, descrevendo o que o programa computa, devendo-se usaruma semântica denotacional. No entanto se o propósito é provar determi-nadas propriedades do sistema, é mais proveitoso basear-se na sua execução,devendo recorrer-se a uma semântica operacional. A semântica axiomáticadeve ser a eleita quando se pretende confirmar a correcção de um dado sis-tema.

Existem duas formas de verificar a correcção de um sistema: verificação

5

de modelos e demonstração de teoremas. A primeira é denominada deverificação semântica, pois baseia-se na comparação da semântica dadapor uma dada especificação com um sistema com um conjunto de estadosfinito — de forma automática (sofrendo eventualmente do problema da ex-plosão de estados). A demonstração de teoremas é apelidada de verificaçãodedutiva. Consiste na utilização de um sistema dedutivo para, de formaparcialmente automática (por vezes é requerida a intervenção do utilizador)provar a correcção de uma dada especificação. Este tipo de verificação jáaceita sistemas com um conjunto de estados potencialmente infinito.

Apesar de existirem várias abordagens e ferramentas distintas para fazerverificação formal, este artigo apenas vai focar um verificador de modelos —o ProB [4] — e um demonstrador de teoremas — o Click’n’Prove [5] —, umavez terem sido as ferramentas elegidas para a realização do trabalho que serádescrito na secção 4. Ambas estas ferramentas trabalham sobre especifica-ções na notação B. O método B foi desenvolvido por Jean-Raymond Abrialem 1996, baseado na sua anterior criação, a notação formal Z. A notação Bcentra-se na especificação de uma máquina abstracta em Abstract MachineNotation — AMN. Esta é uma notação processável por computadores dassubstituições generalizadas — conjunto de teorias matemáticas que recor-rem ao Cálculo Proposicional com Lógica de Primeira Ordem (LPO)e à Teoria dos Conjuntos.

O AMN permite que cada sistema seja representado num modelo de umamáquina, ou de um ou mais refinamentos de um modelo inicial. A especifi-cação de refinamentos garante uma implementação incremental do sistema:começa-se por implementar pequenas porções do sistema que já se provaramcorrectas, sendo que a qualquer momento a implementação está a satisfazer omodelo da máquina inicial. É também uma forma de evitar que a correcçãode um sistema seja verificado apenas após a sua completa implementação.Para verificar cada refinamento é necessário ter em conta um conjunto deproof obligations em particular, relativos ao invariante de ligação (linking in-variant). Este indica qual a relação entre certas variáveis do modelo inicialcom novas variáveis de um refinamento, de forma a garantir que as proprie-dades do refinamento a estas associadas se mantêm correctas.

O processo de utilização das ferramentas de verificação será particulari-zado no projecto desenvolvido, descrito na secção seguinte.

6

4 Sincronização e gestão de contactos pessoais(PIMSync)

O PimSync1 consiste numa ferramenta que, quando instalada num dispo-sitivo com acesso a uma lista de contactos, permite efectuar a sua gestão.Adicionalmente, o PimSync permite sincronizar a lista de contactos com ou-tros dispositivos. O PimSync destina-se a um utilizador que possui diversosdispositivos com listas de contactos (como um telemóvel, um PDA, um PCe um portátil).

4.1 Recolha de requisitos

A lista de requisitos do cliente encontra-se dividida em dois grupos. Osrequisitos seguintes são referentes ao programa final, ao seu funcionamentode forma geral.

R1-1 Um contacto consiste num nome, um número de telefone e um endereçode e-mail;

R1-2 Um dispositivo pode ser um smart-phone, um computador de mesa ouportátil (poderá inclusivamente ser uma aplicação Web), e possui umalista de contactos;

R1-3 Cada dispositivo pode criar um contacto, remover um contacto exis-tente e modificar um contacto existente;

R1-4 O utilizador pode sincronizar um dado dispositivo com outro assu-mindo que há conectividade entre ambos os dispositivos;

R1-5 A sincronização dos contactos é feita com base no momento da últimaactualização do contacto — a versão mais nova de um dado contactoprevalece;

R1-6 Qualquer tipo de conflitos entre dois contactos é ignorado, apenasinteressa o momento da úlltima alteração (i.e., não se dá nenhumafusão entre versões);

R1-7 A remoção de um contacto deixa uma marca na lista de contactos,para que a remoção possa ser propagada para outros dispositivos;

R1-8 A sincronização de dispositivos garante que não ficam contactos emduplicado em cada dispositivo.

1O projecto PimSync é puramente académico.

7

Relativamente ao protocolo de ligação entre dispositivos, existem os se-guintes requisitos:

R1-9 Sempre que um dispositivo consegue alcançar (i.e., possui uma ligaçãocom) outro dispositivo, pode ocorrer uma sincronização entre ambos;

R1-10 Cabe ao utilizador adoptar uma arquitectura com uma base de dadoscentralizada para os contactos (usando um dispositivo como servidorcentral) ou não, uma vez que cada dispositivo pode efectuar uma sin-cronização com outro qualquer;

R1-11 Para prevenir eventuais falhas na comunicação, a sincronização de-verá ser efectuada contacto a contacto (e.g., se a primeira tentativafalhar aos 70%, na seguinte só será necessário sincronizar 30% dos con-tactos);

R1-12 Cada dispositivo só poderá efectuar uma sincronização de cada vez.Se outro dispositivo tentar sincronizar com outro que já esteja a meiode uma sincronização, não lhe será possível continuar, devendo tentarnovamente após uns momentos.

4.2 Análise dos requisitos

Antes de proceder à construção da especificação formal, é necessário processaros requisitos, na sua forma inicial, para que seja mais directa a sua conversãopara uma notação formal. Este é um factor necessário pois o cliente nãoprecisa de ter a noção do que vai ou não ser utilizado, nem tão pouco como éespecificado ou desenhado um sistema. De seguida encontram-se três gruposde requisitos já mais detalhados e trabalhados (e portanto, mais fáceis deespecificar formalmente).

Requisitos relacionados com o modelo de domínio:

R2-1 Cada dispositivo é identificado com um número natural;

R2-2 Um dispositivo contem um conjunto de contactos;

R2-3 Cada contacto está associado a um nome, um número de telefone eum endereço de e-mail, e está identificado por um identificador (umnúmero natural único em cada dispositivo);

R2-4 Cada contacto está associado a uma marca temporal do momento dasua última actualização;

Requisitos relacionados com a gestão dos contactos:

8

R2-5 A adição de um contacto associa um novo identificador a um nome,e-mail e número de telefone;

R2-6 A remoção de um contacto apaga o número de telefone e o e-maildo contacto. Mantém o identificador, para que sejam propagadas asremoções em futuras sincronizações;

R2-7 A alteração de um contacto permite alterar o endereço e-mail e onúmero de telefone de um contacto;

R2-8 A pesquisa de um contacto pelo seu nome retorna o seu identificador;

R2-9 A adição, remoção ou alteração de um contacto actualiza o valor damarca temporal associada ao contacto em causa;

Requisitos relacionados com o protocolo de sincronização:

R2-10 A sincronização de um dispositivo com outro compreende a sincro-nização de cada um dos contactos do segundo dispositivo;

R2-11 Cada dispositivo só efectua sincronizações com um dispositivo decada vez;

R2-12 Cada contacto é sincronizado com base na sua marca temporal deúltima criação/alteração/remoção.

R2-13 Cada dispositivo está associado a um par de uma marca temporalcom um dispositivo, significando o último momento da sincronizaçãodesses dispositivos;

R2-14 Todas as marcas temporais usadas servem apenas para representarrelações de “aconteceu-antes”, ao longo da execução do sistema;

Esta lista de requisitos já faz algumas concessões face aos requisitos inici-ais. Estas permitem controlar a complexidade da especificação2. R2-2 reduza ligação entre os conceitos utilizador, dispositivo, lista e contactosa uma ligação entre dispositivo e contactos, sem perder funcionalidade.Para efeitos da modelação da sincronização, também é assumido que umcontacto tem um nome único em cada dispositivo. Adicionalmente, a especi-ficação irá contemplar apenas o cenário onde não há falhas de comunicaçãonas ligações inter-dispositivo.

2 Num sistema crítico esta redução de complexidade arbitrária não é possível, mas paraefeitos académicos a simplificação resultará numa descrição mais clara do projecto

9

Antes de iniciar a especificação é importante descrever, em linguagemnatural, a semântica operacional do protocolo de sincronização. Os passosseguintes são efectuados quando um dispositivo A faz uma sincronização como dispositivo B:

1. O dispositivo A pede para iniciar a sincronização com o B, e envia-lhe a marca temporal da última sincronização com B. Deixa de podersincronizar com outro dispositivo;

2. O dispositivo B recebe o pedido de A, e fica indisponível para sincro-nizar com outros contactos;

3. O dispositivo B envia um conjunto de pedidos de sincronização de con-tactos para A, para todos os contactos cuja marca temporal de últimaactualização seja superior à marca temporal recebida no passo ante-rior. Junto com cada pedido, é enviado o nome, o e-mail e o númerode telefone do contacto, bem como a marca temporal da sua últimaactualização;

4. O dispositivo B informa o A que terminou o envio dos contactos, e ficade novo disponível para efectuar sincronizações;

5. O dispositivo A processa as informações dos contactos recebidos de B,actualizando os seus dados, as marcas temporais de cada contacto e amarca temporal de sincronização com o dispositivo B;

6. O dispositivo A termina a sincronização, e fica de novo disponível;

4.3 Formalização

O processo de formalização deve começar pela especificação formal do sistemaglobal. Neste caso, o sistema terá de conter o conjunto de dispositivos a seremusados, bem como operações de sincronização entre eles. O ambiente ondecada dispositivo é executado tem de ser modelado neste sistema global, paraque se possa formalizar o comportamento das interacções entre dispositivos.

O anexo em A contém a listagem do código B que especifica o modelodeste sistema. Ao criar esta especificação não foi possível traduzir direc-tamente os requisitos mencionados na secção anterior. Os detalhes que seseguem tiveram de ser considerados com particular atenção. No seguimentodo artigo parte-se do princípio que o leitor está minimamente familiarizadocom a notação AMN usada no B-Method ([6]).

10

Relativamente ao modelo de domínio foi apenas feita uma simplificação,que não afecta o objectivo deste exemplo académico, mas reduz a sua com-plexidade: a adição de que um nome é único em cada dispositivo faz comque a sincronização dos contactos possa ser feita com base na procura decontactos pelos seus nomes.

A sincronização foi feita com o recurso a mensagens. A modelação foifeita com base em conjuntos de mensagens de vários tipos, e cada dispositivopode acrescentar e remover mensagens a esses conjuntos, com base no etapada sincronização em que se encontram. Para limitar que outros dispositivosentrassem em sincronização concorrentemente (satisfazendo o requisito R2-11), foi criada uma função que associa um dispositivo com um estado, comose indica na figura 1.

SETS STATE = {Synchronizing, Idle}INVARIANT currentState: DEVICE --> STATE

Figura 1: Modelação do estado de cada dispositivo

O tempo tem de ser modelado com uma estratégia análoga à dos relógiosde Lamport ([7, 8]), segundo o qual o tempo se representa como uma grandezadiscreta, um contador em software que é incrementado para cada eventodistinto. Assim, para qualquer dois eventos a e b, e sendo C(x) a marcatemporal para o evento x, verifica-se sempre que C(a) 6= C(b). Esta é umasolução para uma limitação conhecida do B-Method, que é a incapacidadede representar o tempo como uma grandeza contínua. Assim sendo, ummarcador temporal é um inteiro natural que vai sendo incrementado numavariável global do sistema (o tempo é global no ambiente de execução). Estaincrementação ocorre de cada vez que há uma operação, e não está associadoà noção de tempo real. A modelação do tempo actual é feita de acordo coma figura 2, e as operações vão ser responsáveis por incrementar este valor(respeitando assim os requisitos R2-12, R2-13 e R2-14).

VARIABLES currentTSINVARIANT currentTS: NAT1

Figura 2: Modelação do tempo de forma discreta.

Existem dois tipos de operações modeladas. Um tipo consiste nas opera-ções executáveis pelos utilizadores dos dispositivos, como o adicionar contactoou o alterar contacto. Estas são especificadas com a cláusula PRE-THEN-END,

11

que representa um conjunto de acções passíveis de serem executadas ao se-rem verificarem determinadas pré-condições, como se verifica na operaçãoda figura 3, que procura um identificador de um contacto num dispositivoatravés do seu nome.

SETSNAME = {NoName, Ann, Beth};EMAIL = {NoEmail, AnnMAIL, BethMAIL};PHONE = {NoPhone, AnnPHONE, BethPHONE};DEVICE = {Laptop, Desktop, Pda}

INVARIANTnames: DEVICE --> (NAT1 >+> NAME) &emails: DEVICE --> (NAT1 +-> EMAIL) &phones: DEVICE --> (NAT1 +-> PHONE) &lastChangeTS: DEVICE --> (NAT1 +-> NAT1)

OPERATIONScontactId <-- lookupContactByName (device, name) =

PREdevice: DEVICE &

device: dom(names) &name: NAME &card(names(device)) > 0 &name: ran(names(device))

THENcontactId:= names(device)~(name)

END;

Figura 3: Especificação formal da operação de pesquisa de contactos.

Por outro lado, os eventos processados automaticamente por cada disposi-tivo devem ocorrer sempre que seja verificado um conjunto de pré-condições.Para isso, estes eventos são modelados com cláusulas SELECT-THEN-ELSE.Esta seria a grande diferença entre ambas as cláusulas, segundo Abrial — noprimeiro caso, não é garantido que a operação só seja executada caso a pré-condição seja satisfeita, apenas não está especificado se a execução é correctano caso contrário. No entanto, a especificação de eventos fica desnecessari-amente confusa, uma vez que existem condições que Abrial não aceita nascláusulas SELECT, como a declaração de tipos das variáveis. A solução utili-zada consiste num encadeamento de uma cláusula do tipo SELECT dentro deuma do tipo PRE. Praticamente, o ProB permite simular o comportamentode ambas as operações, permitindo executar tanto operações do utilizador

12

como eventos — cabe ao utilizador fazer o papel de controlador do ambientee forçar a execução dos eventos, como no exemplo da operação na figura 4,que representa o passo 2 do protocolo de sincronização.

SETSSTATE = {Synchronizing, Idle}

INVARIANTstartSyncMsgs: DEVICE --> (DEVICE +-> NAT) &endSyncMsgs: DEVICE --> FIN(DEVICE) &syncMsgsId: DEVICE --> (DEVICE --> (NAT +-> NAT)) &syncNameMsgs: NAT +-> NAME &syncMailMsgs: NAT +-> EMAIL &syncPhoneMsgs: NAT +-> PHONE &syncTSMsgs: NAT +-> NAT1 &contactMsgsCounter : NAT1

OPERATIONSSyncFromDevice (destDevice, origDevice) =

PREorigDevice : DEVICE &destDevice : DEVICE &origDevice /= destDevice

THENSELECTcurrentState(destDevice) = Idle &origDevice : dom(startSyncMsgs(destDevice))

THENcurrentState(destDevice) := Synchronizing

ENDEND;

Figura 4: Segundo passo do protocolo de sincronização

Não existe o conceito de uniões na especificação de estruturas de dados.Como tal, recorreu-se a estratagemas para representar múltiplas entidades,como múltiplos conjuntos ao invés de um só (explicitando a decisão de qual otipo de cada elemento no corpo da operação que o utiliza/modifica, ao invésde estar implícito na sua utilização). Um exemplo pode ser encontrado nasmensagens trocadas no protocolo de sincronização. No exemplo da figura 5,correspondente à operação relativa ao passo 1 do protocolo de sincronização,

13

é enviada uma mensagem do tipo startSyncMsgs.

OPERATIONSSyncWithDevice (origDevice, destDevice) =

PREorigDevice : DEVICE &destDevice : DEVICE &origDevice /= destDevice &currentTS <= max_time &currentState(origDevice) = Idle

THENcurrentState(origDevice) := Synchronizing ||startSyncMsgs(destDevice) := startSyncMsgs(destDevice) \/

{origDevice |-> lastSyncTS(origDevice)(destDevice)}END;

Figura 5: Primeiro passo da sincronização

Idealmente haveria um conjunto de entidades para representar um buf-fer de mensagens enviadas (e outro idêntico para as recebidas), para cadadispositivo. Na especificação apresentada estão modelados um conjunto pordispositivo para cada tipo de mensagem, pois só assim se podem detectarque há diferentes tipos de mensagens. É um motivo análogo que levou aespecificar um contacto recorrendo às relações indicadas na figura 6, quepermitem identificar um atributo através da identificação do dispositivo e doidentificador do contacto.

INVARIANTnames: DEVICE --> (NAT1 +-> NAME) &emails: DEVICE --> (NAT1 +-> EMAIL) &phones: DEVICE --> (NAT1 +-> PHONE) &lastChangeTS: DEVICE --> (NAT1 +-> NAT1)

Figura 6: Especificação do conceito ’contacto’

4.4 Verificação

Tendo a especificação formalizada, a etapa seguinte consiste na verificaçãodo modelo do PimSync, seguida da sua verificação dedutiva (com o recursoao demonstrador de teoremas).

14

4.4.1 Verificador de modelos interactivo

O verificador de modelos utilizado foi o ProB. Esta ferramenta permite carre-gar o modelo da máquina e gerar, automaticamente, uma máquina de estadospassíveis de serem alcançados. Com base nesta é viável executar as transiçõesde estados por uma ordem arbitrária, verificando se os estados resultantescorrespondem ou não às expectativas. Consegue-se, assim, verificar a cor-recção operacional da especificação. Do ponto de vista de interacção com ocliente, torna também possível a verificação de casos de uso, podendo simulara execução de uma sequência de operações.

Uma outra vantagem do verificador de modelos é a capacidade de verificara sintaxe do modelo formal. A desvantagem é que a complexidade do modeloresulta regularmente num período de tempo de verificação formal e de geraçãoda máquina de estados muito longo. Em particular, predicados complexos(e.g., com quantificadores universais ou existenciais) no invariante do modeloaumentam grandemente a complexidade da verificação.

Para colmatar a inevitável explosão de estados de uma geração auto-mática da máquina de estados relativa a um modelo teórico, é necessáriointroduzir restrições aos conjuntos utilizados. O verificador permite restrin-gir o conjunto dos números naturais com, por exemplo, 5 elementos, o que ésuficiente para experimentar a maioria dos casos de uso. No entanto, conjun-tos com elementos de outros tipos específicos devem ser limitados, também.Uma vez que estes serão usados para confirmar os estados da máquina, con-vém atribuir nomes lógicos aos elementos. Assim, a definição dos conjuntospode ser feita como indicado na figura 7.

SETSNAME = {NoName, Ann, Beth};EMAIL = {NoEmail, AnnMAIL, BethMAIL};PHONE = {NoPhone, AnnPHONE, BethPHONE};DEVICE = {Laptop, Desktop, Pda};STATE = {Synchronizing, Idle}

Figura 7: Definição de conjuntos com restrições

Uma vez conscientes dos problemas mencionados, a verificação dos mo-delos torna-se bastante útil para o desenvolvimento da especificação. Existe,todavia, uma outra desvantagem na utilização do ProB. A verificação sintác-tica que é efectuada não garante (1) a correcção semântica do sistema nem(2) que a sintaxe que é considerada correcta seja aceite pelo demonstrador deteoremas. Apesar destes factores, e dada a facilidade associada ao processo

15

de verificação interactiva, compensa usar uma ferramenta desta categoriaantes de passar ao demonstrador.

A utilização do verificador do ProB com o PimSync provou que é viá-vel desenvolver a especificação iterativamente, operação a operação: umavez acertada a forma correcta de especificar o comportamento indicado nosrequisitos para dada operação, procede-se com a modelação dos requisitosrestantes.

Uma vez tendo o modelo da máquina especificado, passa-se à demons-tração dos teoremas que conduzem à verificação da sua correcção. A sec-ção seguinte descreve a utilização de um demonstrador de teoremas com oPimSync.

4.4.2 Demonstrador de teoremas

O Click’n’Prove é um demonstrador de teoremas que utiliza os mecanismosde dedução e inferência do Atelier-B. A sua grande vantagem é a interfacedo utilizador que é, de acordo com os autores, mais avançada e produtiva.Esta encontra-se definida como um programa escrito para o editor de textoEmacs, e consiste apenas num conjunto de operações que podem ser acedidasatravés de botões. Na prática esta interface, apesar de incluir as operaçõesnecessárias para efectuar as demonstrações, não é fácil de operar para umgrande número de eventuais interessados. A falta de portabilidade (apenascorre em sistemas Linux) e o componente textual (ao invés do aspecto grá-fico trabalhado a que as aplicações nos têm vindo a habituar ao longo dosanos) coloca entraves a muitos utilizadores. Este facto não está, no entanto,relacionado com o desempenho das funcionalidades da ferramenta.

O mecanismo de demonstrações interactivas do Click’n’Prove permiteanalisar modelos e gerar um conjunto de hipóteses para serem provadas.Destas hipóteses, algumas conseguem ser demonstradas automaticamente,enquanto que as restantes necessitam da intervenção do utilizador para se-rem demonstradas. Esta interacção permite que o utilizador escolha quaisas hipóteses a serem utilizadas pelo demonstrador3 e também que adicionenovas hipóteses — ou remova algumas existentes. Adicionalmente, permiteusar três tipos de demonstradores automáticos, para actuarem com base nashipóteses consideradas.

As hipóteses que o demonstrador não consegue provar automaticamentepodem ser agrupadas nas categorias seguintes:

Insuficiências do demonstrador Acontecem quando este não consegue3As hipóteses são mantidas em vários níveis de acesso, funcionando como vários níveis

de memória cache numa arquitectura de um computador.

16

concluir sobre determinada asserção lógica, com as hipóteses que játem na sua base de conhecimento. Estes teoremas são prováveis inte-ractivamente, fornecendo a ajuda necessária ao mecanismo de dedução.São estas as provas que se encontram descritas no anexo em B;

Erro na especificação Caso a especificação esteja errada, o demonstradoré levado a tentar provar uma hipótese que está errada, conduzindo-seassim a um beco sem saída. A única solução é alterar a especificação, sebem que o demonstrador permite assumir que a hipótese está correcta eprosseguir (podendo voltar atrás para a provar no futuro, o que voltariaa resultar numa hipótese não demonstrável);

Incompatibilidade sintáctica Por vezes o demonstrador apresenta pro-blemas em processar determinadas construções sintácticas. Neste casoé possível reformulá-las para uma alternativa equivalente e repetir ademonstração;

Incapacidade do demonstrador Há casos em que a construção, apesarde formalmente correcta, não está de acordo com as formas sintácticasque o demonstrador reconhece e sabe manipular. Neste caso a únicasolução é reformular as frases em causa, na especificação.

A versão final da especificação formal do PimSync gerou 254 hipóteses,das quais 75 requereram intervenção manual interactiva para as demonstrar.

Houve erros facilmente detectáveis que não haviam sido encontrados como verificador interactivo. Um deles é a incapacidade de provar que uma funçãotinha uma inversa. Este erro derivava de uma especificação errada, pois haviadeclarado mal a invariante associada ao conjunto names, declarando-a comouma função parcial e não como uma função injectiva parcial (que já garanteter uma inversa). Este problema foi corrigido alterando o operador de funçãoparcial (+->) para o de função parcial injectiva (>+>).

Um exemplo de um caso problemático para o demonstrador consiste naunião de um conjunto que pode ser vazio com outro conjunto. A soluçãoneste caso consiste em separar, numa cláusula IF-THEN-ELSE-END, compor-tamentos diferentes para quando o conjunto se encontra vazio ou não, comose pode ver na figura 8, num extracto da operação EndSyncWithDevice.

Um outro detalhe derivado de uma limitação do demonstrador é a insufi-ciência da sua base conhecimento, no que respeita às regras de derivação. Atítulo de exemplo, o demonstrador conhece duas notações para os númerosnaturais (NAT e NATURAL). No entanto, por vezes não consegue estabeleceruma relação de pertença entre ambos os conjuntos, sendo necessário indicar-lhe isso, com a instrução NAT <: NATURAL.

17

IF card(endSyncMsgs(destDevice)) > 0THEN

endSyncMsgs(destDevice) :=endSyncMsgs(destDevice) \/ {origDevice}

ELSEendSyncMsgs(destDevice) := {origDevice}

END ||

Figura 8: Estratégia para ajudar o demonstrador

Das restantes demonstrações interactivas, destaca-se a necessidade de in-dicar os domínios de relações complexas. Por exemplo, o demonstrador con-seguia processar o domínio de names(device), mas apresenta problemas comnames(device)(contactId).

Um aspecto curioso foi a quantidade de hipóteses repetidas que o de-monstrador obrigou a provar. A sua implementação obriga a percorrer todosos caminhos. Uma vez que a inclusão de eventos com condições aumentagrandemente o número de caminhos análogos (e portanto o número de proof-obligations também análogas), a incapacidade de resolver uma delas gera umconjunto de hipóteses semelhantes por demonstrar.

4.5 Observações

Este projecto permitiu tomar conhecimento do trabalho envolvido no de-senvolvimento formal de software. A análise dos requisitos e a consequenteescrita da primeira versão do modelo formal é, sem dúvida, a etapa maisimportante do projecto, dado que é com base nesta que se estabelece a com-plexidade do restante. Não só permite obter um conjunto de requisitos numalinguagem universal e remover eventuais ambiguidades entre a equipa de de-senvolvimento e o cliente, como também aumenta o conhecimento sobre odomínio do problema a modelar.

A verificação do modelo da especificação mostrou ser de grande utilidade.O simples facto de conseguir simular a execução das operações especificadasno modelo formal da máquina abstracta e de ver as alterações que esta pro-voca nas variáveis de estado permite confirmar de uma forma rápida se ocomportamento aparente está correcto. Este é um grau de tolerância que jádetecta muitas falhas, sejam sintácticas ou semânticas, e que permite que omodelo que se vai tentar provar correcto com o demonstrador de teoremasnão tenha falhas grandes. Apesar disto, a ferramenta ProB apresenta enor-mes entraves ao desenvolvimento, não só por ser um editor de código muito

18

fraco — e sem seguir muitas das normas de interfaces — mas por bloquearassim que carrega um ficheiro, para que seja verificada a sua sintaxe e geradaa máquina de estados para este. Este último detalhe é relevante pois os atra-sos chegaram a ser de 10 a 15 minutos4, sempre que o invariante da máquinaera mais complexo (e.g., de forma a incluir quantificadores universais). Asolução encontrada para optimizar esta actividade foi encontrar um editorde código alternativo (como o JEdit, ou o Emacs) de tal modo que fossepossível continuar a analisar e corrigir a máquina enquanto o ProB carregavauma versão desta. No entanto, os recursos do sistema ficam, durante essecarregamento, muito escassos.

A experiência resultante da demonstração da especificação do PimSyncnão foi tão gratificante quando seria de supor. A tarefa foi tediosa, uma vezque as hipóteses auxiliares tiveram de ser introduzidas manualmente paracada teorema que se pretendia demonstrar — seria preferível que fossemguardadas para futuras demonstrações. Adicionalmente, a interface para outilizador não cumpre os seus objectivos. Como interface gráfica é muito po-bre, os botões não são muito funcionais, a falta de indicações e ajudas visuaisé notória e, para colmatar, sofre de bloqueios de interface em determinadasoperações. Por outro lado, uma vez que é uma interface no ambiente Emacs,é desprovido de atalhos de teclado para executar os comandos (ou da indica-ção da sua presença), factor esse que é familiar para os utilizadores do Emacs(e tende a aumentar a sua produtividade).

Os problemas todos relativos à verificação da especificação do sistemaindicam que esta será proveitosa para sistemas críticos, como sistemas de-pendentes do tempo real (real-time systems), ou controladores de aparelhoscomplexos. Para aplicações de pequeno ou médio porte este esforço podenão compensar, sendo mais produtivo criar uma implementação que possater testada com o cliente e corrigida, se necessário. No entanto, a facilidadeda compreensão da linguagem formal (visto usar apenas um reduzido sub-conjunto da notação matemática, nomeadamente a Teoria de Conjuntos e oCálculo Proposicional) torna a criação de uma especificação uma mais valiaaconselhável para qualquer tipo de projecto, na sua envolvente da Engenhariade Requisitos.

Por último, há que constatar que as dificuldades apresentadas com osuporte obtido com as ferramentas (associada às restrições temporais im-postos ao projecto), impediram a realização de duas importantes etapas nodesenvolvimento — os refinamentos e a implementação de um programa quesatisfaça a especificação. A especificação de um refinamento teria envolvido

4Tempos obtidos num Pentium IV a 2.0 GHz, com 768 MB de RAM, correndo oWindows Server 2003.

19

provar (para além da correcção do modelo refinado) as proof obligations re-lativas ao invariante de ligação. Um aspecto muito importante a reter é quesem a implementação é impossível garantir que o que foi especificado vai aoencontro das expectativas do cliente — apenas se sabe que o uma implemen-tação que satisfaça a especificação irá corresponder aos requisitos acordadoscom o cliente.

5 ConclusãoO desenvolvimento formal de software tem vindo a ser desenvolvido, podendoser já integrado nas várias etapas do ciclo de desenvolvimento de um sistema.Neste trabalho criou-se uma especificação formal de um sistema de sincro-nização de contactos entre dispositivos, partindo dos requisitos iniciais docliente, e procedeu-se à verificação do modelo criado. Esta verificação teveduas etapas, a verificação interactiva do modelo, recorrendo à ferramentaProB, e a demonstração de teoremas, recorrendo ao Click’n’Prove.

De todo o processo conclui-se a extrema utilidade da especificação formaldos requisitos, não só como forma de chegar a um acordo com o cliente mastambém para conhecer minuciosamente o domínio do projecto. A verificaçãodo modelo criado confere, sem dúvida, uma garantia da correcção do modelo.Mas o seu processo pode não justificar o esforço que implica para um grandenúmero de projectos (desde que não sejam críticos, ou de uma grande escala,por exemplo), pois o suporte das ferramentas utilizadas deixa muito a de-sejar. Este é um dos factores que justificam a falta de adesão aos métodosformais no desenvolvimento de software, salvo em casos particulares ou nomeio académico.

Referências[1] J. M. Wing, A specifier’s introduction to formal methods. Computer,

Volume 23, No. 9, pp. 8–23, 1990.

[2] A. Hall, Seven myths of formal methods. IEEE Softw., Volume 7, No. 5,pp. 11–19, 1990.

[3] H. R. Nielson e F. Nielson, Semantics with applications: a formal intro-duction, New York, NY, USA: John Wiley & Sons, Inc., 1992.

[4] M. Leuschel e M. Butler, Prob: A model checker for b, 2003. Disponivelem: citeseer.ist.psu.edu/leuschel03prob.html.

20

[5] J.-R. Abrial e D. Cansell, Click’n prove — interactive proofs within settheory. 2003.

[6] S. Schneider, The B-Method: An Introduction, Palgrave, 2002. Disponivelem: http://www.palgrave.com/science/computing/schneider/.

[7] M. Abadi e L. Lamport, An old-fashioned recipe for real time, Em Proce-edings of the Real-Time: Theory in Practice, REX Workshop, pp. 1–27,London, UK: Springer-Verlag, 1992.

[8] L. Lamport, Time, clocks, and the ordering of events in a distributedsystem. Commun. ACM, Volume 21, No. 7, pp. 558–565, 1978.

[9] M. Butler e J. Falampin, An approach to modelling and refining timingproperties in b. 2001.

21

A Anexo — A especificação formal do modeloA seguinte listagem contém o código da especificação formal, na notção AMNusada no método B.

/*This machine contains a system that holds several devices, each with acontact list. Each device is able to synchronize it’s contacts with another.*/

MACHINE PimSyncSystem (max_time)

SETSNAME = {NoName, Ann, Beth};EMAIL = {NoEmail, AnnMAIL, BethMAIL};PHONE = {NoPhone, AnnPHONE, BethPHONE};DEVICE = {Laptop, Desktop, Pda};STATE = {Synchronizing, Idle}

CONSTRAINTS

max_time : NAT & max_time = 1000

CONSTANTSmaxID

PROPERTIESmaxID : (FIN(NAT)) --> NAT &!(ll).(ll:FIN(NAT) & card(ll) = 0 => maxID(ll) = 0) &!(ll).(ll:FIN(NAT) & card(ll) > 0 => maxID(ll) = max(ll))

VARIABLESnames, emails, phones, lastChangeTS, lastSelfSyncTS, lastSyncTS,currentTS, currentState, startSyncMsgs, endSyncMsgs, syncMsgsId,syncNameMsgs, syncMailMsgs,syncPhoneMsgs, syncTSMsgs, contactMsgsCounter

INVARIANTnames: DEVICE --> (NAT >+> NAME) &emails: DEVICE --> (NAT +-> EMAIL) &phones: DEVICE --> (NAT +-> PHONE) &lastChangeTS: DEVICE --> (NAT +-> NAT) &lastSyncTS: DEVICE --> (DEVICE +-> NAT) &lastSelfSyncTS:DEVICE --> NAT &currentState: DEVICE --> STATE &startSyncMsgs: DEVICE --> (DEVICE +-> NAT) &endSyncMsgs: DEVICE --> FIN(DEVICE) &syncMsgsId: DEVICE --> (DEVICE --> (NAT +-> NAT)) &syncNameMsgs: NAT +-> NAME &syncMailMsgs: NAT +-> EMAIL &syncPhoneMsgs: NAT +-> PHONE &syncTSMsgs: NAT +-> NAT &currentTS: NAT &currentTS <= max_time &contactMsgsCounter : NAT &dom(names) = dom(emails) &dom(names) = dom(phones) &dom(names) = dom(lastChangeTS) &dom(phones) = dom(lastChangeTS) &dom(emails) = dom(lastChangeTS) &dom(syncMailMsgs) = dom(syncNameMsgs) &dom(syncMailMsgs) = dom(syncPhoneMsgs) &dom(syncMailMsgs) = dom(syncTSMsgs)

INITIALISATIONlastSyncTS := {Pda |-> {Laptop |-> 1, Desktop |-> 1},

Laptop |-> {Pda |-> 1, Desktop |-> 1},Desktop |-> {Pda |-> 1, Laptop |-> 1}} ||

lastSelfSyncTS:= {Pda |-> 1, Laptop |-> 1, Desktop |-> 1} ||

currentState := {Pda |-> Idle, Laptop |->Idle, Desktop |-> Idle} ||

lastChangeTS := {Pda |-> {}, Laptop |->{}, Desktop |-> {}} ||names := {Pda |-> {}, Laptop |->{}, Desktop |-> {}} ||emails := {Pda |-> {}, Laptop |->{}, Desktop |-> {}} ||phones := {Pda |-> {}, Laptop |->{}, Desktop |-> {}} ||

startSyncMsgs := {Laptop |->{}, Desktop|->{}, Pda|->{}} ||endSyncMsgs := {Laptop |->{}, Desktop|->{}, Pda|->{}} ||

22

currentTS := 1 ||

syncNameMsgs := {} ||syncMailMsgs := {} ||syncPhoneMsgs := {} ||syncTSMsgs := {} ||syncMsgsId := {Pda |-> {Pda |-> {}, Laptop |-> {}, Desktop |-> {}},

Laptop |-> {Pda |-> {}, Laptop |-> {}, Desktop |-> {}},Desktop |-> {Pda |-> {}, Laptop |-> {}, Desktop |-> {}}} ||

contactMsgsCounter := 1

OPERATIONS

/*************************************************************************//* Contacts Management Operations *//* - this operations are to be executed by the user *//*************************************************************************/

addContact (device, name, email, phone) =PREdevice : DEVICE &name : NAME &email: EMAIL &phone: PHONE &device : dom(emails) &currentTS < max_time &email /= NoEmail & phone /= NoPhone &maxID(dom(lastChangeTS(device))) < 100 &currentState(device) = Idle &name /: ran(names(device)) &not (#(ii).(ii: NAT & ii: dom(names(device)) &

((names (device)(ii) = name) &(emails(device)(ii) = email) &(phones(device)(ii) = phone)))) &

(card(lastChangeTS(device)) > 0 => maxID(dom(lastChangeTS(device)))+1 /: dom(names(device))) &(card(lastChangeTS(device)) > 0 => maxID(dom(lastChangeTS(device)))+1 /: dom(emails(device))) &(card(lastChangeTS(device)) > 0 => maxID(dom(lastChangeTS(device)))+1 /: dom(phones(device))) &(card(lastChangeTS(device)) > 0 => maxID(dom(lastChangeTS(device)))+1 /: dom(lastChangeTS(device)))THENIF card(lastChangeTS(device)) > 0THEN/* The sets are not empty, so we add something to them: */emails(device) := emails(device) \/ {maxID(dom(lastChangeTS(device)))+1 |-> email} ||phones(device) := phones(device) \/ {maxID(dom(lastChangeTS(device)))+1 |-> phone} ||names(device) := names(device) \/ {maxID(dom(lastChangeTS(device)))+1 |-> name} ||lastChangeTS(device) := lastChangeTS(device) \/ {maxID(dom(lastChangeTS(device)))+1 |-> currentTS}ELSE/* The sets are still empty, so we create the first entry: */emails(device) := {1 |-> email} ||phones(device) := {1 |-> phone} ||names(device) := {1 |-> name} ||lastChangeTS(device) := {1 |-> currentTS}END ||lastSelfSyncTS(device) := currentTS ||currentTS := currentTS + 1END;

removeContact (device, ii) =PREdevice : DEVICE & device : dom(names) &currentTS < max_time &currentState(device) = Idle &ii : NAT & ii : dom(names(device)) &(phones(device)(ii) /= NoPhone oremails(device)(ii) /= NoEmail)THENemails(device)(ii) := NoEmail ||phones(device)(ii) := NoPhone ||lastChangeTS(device)(ii) := currentTS ||lastSelfSyncTS(device) := currentTS ||currentTS := currentTS + 1END;

changeContactPhone (device, ii, phone) =PREdevice: DEVICE &device: dom(phones) &phone: PHONE &

23

currentState(device) = Idle &currentTS < max_time &ii : NAT &ii : dom(phones(device)) &phones(device)(ii) /= phone &emails(device)(ii) /= NoEmail & phones(device)(ii) /= NoPhoneTHENphones(device)(ii) := phone ||lastChangeTS(device)(ii) := currentTS ||lastSelfSyncTS(device) := currentTS ||currentTS := currentTS + 1END;

changeContactEmail (device, ii, email) =PREdevice: DEVICE &device: dom(emails) &email: EMAIL &currentState(device) = Idle &currentTS < max_time &ii : NAT &ii : dom(emails(device)) &emails(device)(ii) /= email &emails(device)(ii) /= NoEmail & phones(device)(ii) /= NoPhoneTHENemails(device)(ii) := email ||lastChangeTS(device)(ii) := currentTS ||lastSelfSyncTS(device) := currentTS ||currentTS := currentTS + 1END;

contactId <-- lookupContactByName (device, name) =PREdevice: DEVICE & device : dom(names) & name: NAME & card(names(device)) > 0 & name: ran(names(device))THENcontactId:= names(device)~(name)END;

SyncWithDevice (origDevice, destDevice) =PREorigDevice : DEVICE &destDevice : DEVICE &origDevice /= destDevice &destDevice : dom(startSyncMsgs) &currentTS < max_time &currentState(origDevice) = Idle &origDevice /: dom(startSyncMsgs(destDevice))THENcurrentState(origDevice) := Synchronizing ||IF card(startSyncMsgs(destDevice)) > 0THENstartSyncMsgs(destDevice) := startSyncMsgs(destDevice) \/ {origDevice |-> lastSyncTS(origDevice)(destDevice)}ELSEstartSyncMsgs(destDevice) := {origDevice |-> lastSyncTS(origDevice)(destDevice)}ENDEND;

/*************************************************************************//* Synchronization Operations *//* - this operations are to be executed by each device, automatically *//*************************************************************************/

/* Event triggered after receiving SyncWithDevice (the first request) */SyncFromDevice (destDevice, origDevice) =PREorigDevice : DEVICE &destDevice : DEVICE &origDevice /= destDeviceTHENSELECTcurrentState(destDevice) = Idle &origDevice : dom(startSyncMsgs(destDevice))THENcurrentState(destDevice) := SynchronizingENDEND;

/* Event triggered for each contact that needs sync */SyncContactWithDevice (origDevice, contactId, destDevice) =

24

PREorigDevice : DEVICE &destDevice : DEVICE &contactId : NAT &contactMsgsCounter + 1 : NAT &lastChangeTS(origDevice) : (NAT +-> NAT)THENSELECTorigDevice /= destDevice &currentState(origDevice) = Synchronizing &destDevice : dom(startSyncMsgs(origDevice)) &contactId : dom(names(origDevice)) &contactId : dom(lastChangeTS(origDevice)) &/* check if this ID wasn’t sent yet: */contactId /: ran(syncMsgsId(destDevice)(origDevice)) &/* check if this contact is to be updated for destDevice: */lastChangeTS(origDevice)(contactId) > startSyncMsgs(origDevice)(destDevice)THENcontactMsgsCounter := contactMsgsCounter + 1 ||syncMsgsId(destDevice)(origDevice) := {contactMsgsCounter |-> contactId} ||syncNameMsgs(contactMsgsCounter) := names(origDevice)(contactId) ||syncMailMsgs(contactMsgsCounter) := emails(origDevice)(contactId) ||syncPhoneMsgs(contactMsgsCounter) := phones(origDevice)(contactId) ||syncTSMsgs(contactMsgsCounter) := lastChangeTS(origDevice)(contactId) ||lastSyncTS(origDevice)(destDevice):=

max({lastSyncTS(origDevice)(destDevice), lastChangeTS(origDevice)(contactId)})ENDEND;

/* What to do when a contact update information is received */SyncContactFromDevice (destDevice, msgID, origDevice) =PREorigDevice : DEVICE &destDevice : DEVICE &maxID(dom(lastChangeTS(destDevice))) < 100 &msgID : NATTHENSELECTorigDevice /= destDevice &currentState(destDevice) = Synchronizing &destDevice : dom(syncMsgsId) &origDevice : dom(syncMsgsId(destDevice)) &maxID(dom(lastChangeTS(destDevice)))+1 /: dom(names(destDevice)) &maxID(dom(lastChangeTS(destDevice)))+1 /: dom(phones(destDevice)) &maxID(dom(lastChangeTS(destDevice)))+1 /: dom(emails(destDevice)) &maxID(dom(lastChangeTS(destDevice)))+1 /: dom(lastChangeTS(destDevice)) &

/* check if this ID hasn’t already been received */msgID : dom(syncMsgsId(destDevice)(origDevice))

THEN /* remove the message from the queue */syncMsgsId(destDevice)(origDevice) :=

syncMsgsId(destDevice)(origDevice) - {msgID |-> syncMsgsId(destDevice)(origDevice)(msgID)} ||lastSyncTS(destDevice)(origDevice) :=

max({lastSyncTS(destDevice)(origDevice), syncTSMsgs(msgID)}) ||lastSelfSyncTS(destDevice) := max({lastSyncTS(destDevice)(origDevice), syncTSMsgs(msgID)}) ||

IF syncNameMsgs(msgID) : ran(names(destDevice))THEN /* remove or change the contact */

LET contactID BE contactID = names(destDevice)~(syncNameMsgs(msgID)) INemails(destDevice)(contactID) := syncMailMsgs(msgID) ||phones(destDevice)(contactID) := syncPhoneMsgs(msgID) ||lastChangeTS(destDevice)(contactID) := syncTSMsgs(msgID)

ENDELSE /* add the contact */

LET contactID BE contactID = maxID(dom(lastChangeTS(destDevice)))+1 IN/* the following assures Click n Prove knows how to handle the unions proeprly: */IF card(names(destDevice)) > 0

THEN names(destDevice) := names(destDevice) \/ {contactID |-> syncNameMsgs(msgID)}ELSE names(destDevice) := {contactID |-> syncNameMsgs(msgID)}END ||

IF card(emails(destDevice)) > 0THEN emails(destDevice) := emails(destDevice) \/ {contactID |-> syncMailMsgs(msgID)}ELSE emails(destDevice) := {contactID |-> syncMailMsgs(msgID)}END ||

IF card(phones(destDevice)) > 0THEN phones(destDevice) := phones(destDevice) \/ {contactID |-> syncPhoneMsgs(msgID)}ELSE phones(destDevice) := {contactID |-> syncPhoneMsgs(msgID)}END ||

IF card(lastChangeTS(destDevice)) > 0THEN lastChangeTS(destDevice):= lastChangeTS(destDevice) \/ {contactID |-> syncTSMsgs(msgID)}

25

ELSE lastChangeTS(destDevice):= {contactID |-> syncTSMsgs(msgID)}END

ENDENDENDEND;

/* Note: This must be the very last function to be called. It is up to the machine to do that. */

/* Event triggered after having all contacts updated */EndSyncWithDevice (origDevice, destDevice) =PREorigDevice : DEVICE &destDevice : DEVICETHENSELECTorigDevice /= destDevice &currentState(origDevice) = Synchronizing &destDevice: dom(startSyncMsgs(origDevice)) &not (#(cc).(cc: NAT &

cc: dom(names(origDevice)) &lastChangeTS(origDevice)(cc) > startSyncMsgs(origDevice)(destDevice) &

cc/: ran(syncMsgsId(destDevice)(origDevice))))THENcurrentState(origDevice) := Idle ||IF card(endSyncMsgs(destDevice)) > 0THENendSyncMsgs(destDevice) := endSyncMsgs(destDevice) \/ {origDevice}ELSEendSyncMsgs(destDevice) := {origDevice}END ||LET xx BE xx=startSyncMsgs(origDevice)(destDevice) IN

startSyncMsgs(origDevice) := startSyncMsgs(origDevice) - {destDevice |-> xx}ENDENDEND;

/* Event triggered after having received EndSyncWithDevice (closing the sync protocol) */EndSyncFromDevice (destDevice, origDevice) =PREorigDevice : DEVICE &destDevice : DEVICETHENSELECTorigDevice /= destDevice &currentState(destDevice) = Synchronizing &origDevice : endSyncMsgs(destDevice) &/* Check if all contacts have been synced */card(syncMsgsId(destDevice)(origDevice)) = 0THENcurrentState(destDevice) := Idle ||endSyncMsgs(destDevice) := endSyncMsgs(destDevice) - {origDevice}ENDEND

END

26

B Anexo — Demonstração dos teoremasEste anexo contém os vários tipos de demonstrações interactivas efectuadasno Click’n’Prove. Cada demonstração contém uma descrição e a árvore dademonstração obtida no final da prova. Para mais informações sobre oscomandos apresentados, e a sua notação, consultar [5].

B.1 Teorema 1

Os conjuntos de tipos complexos não são reconhecidos automáticamente.Concretamente, a inicialização da máquina tenta atribuir a uma variável umvalor com funções no contra-domínio de outras funções, e o Click’n’Provenão reconhece a que conjunto pertence essa variável.

[mk] cl[zm] /*{Pda|->{Pda|->{},Laptop|->{},Desktop|->{}},

Laptop|->{Pda|->{},Laptop|->{},Desktop|->{}},Desktop|->{Pda|->{},Laptop|->{},Desktop|->{}}}

: DEVICE +-> (DEVICE --> (NAT +-> NAT))*/[mk] xp[mk] ah({Pda|->{},Laptop|->{},Desktop|->{}} :

DEVICE --> (NAT +-> NAT))[zm] /*{Pda|->{},Laptop|->{},Desktop|->{}} :

DEVICE --> (NAT +-> NAT)*/[mk] pr[mk] pr/*SUCCESS*/

B.2 Teorema 2O Click’n’Prove não reconhece os domínios de dois conjuntos, após seremrestritos com condições complexas.

[mk] cl[zm] /*dom(syncMailMsgs<+

{contactMsgsCounter|->emails(origDevice)(contactId)})= dom(syncNameMsgs<+

{contactMsgsCounter|->names(origDevice)(contactId)})*/

[mk] nv(dom(syncMailMsgs) = dom(syncNameMsgs))[mk] xp[mk] ah(emails(origDevice) : NAT +-> EMAIL)[zm] /*emails(origDevice) : NAT +-> EMAIL*/[mk] pr[mk] ah(emails(origDevice)(contactId) : EMAIL)[zm] /*emails(origDevice)(contactId) : EMAIL*/[mk] pr[mk] ds(destDevice : dom(startSyncMsgs(origDevice)))[mk] ds(currentState(origDevice) = Synchronizing)[mk] ah(names : DEVICE +-> (NAT >+> NAME))[mk] pp(rp.0)/*SUCCESS*/

27

B.3 Teorema 3Nesta prova, o Click’n’Prove identifica mal o domínio de um conjunto, tentaprovar que endSyncMsgs(destDevice):FIN(INTEGER).

[mk] cl[zm] /*endSyncMsgs<+

{destDevice|->endSyncMsgs(destDevice)-{origDevice}}: DEVICE +-> FIN(DEVICE)*/

[mk] xp[mk] ah(endSyncMsgs(destDevice) : FIN(DEVICE))[zm] /*endSyncMsgs(destDevice) : FIN(DEVICE)*/[mk] pr[mk] pr/*SUCCESS*/

B.4 Teorema 4Ao tentar demontrar este teorema, o Click’n’Prove não reconhece o inversode uma função sem a simplificação de um conjunto.

[mk] cl[zm] /*lastChangeTS<+{destDevice|->(lastChangeTS(destDevice)<+

{contactID|->syncTSMsgs(msgID)})} :DEVICE +-> (NAT +-> NAT)*/

[mk] xp[zm] /*lastChangeTS<+{destDevice|->

(lastChangeTS(destDevice)<+{names(destDevice)~(syncNameMsgs(msgID))|->

syncTSMsgs(msgID)})} :DEVICE +-> (NAT +-> NAT)*/

[mk] ah(names(destDevice)~(syncNameMsgs(msgID)) : NAT)[zm] /*names(destDevice)~(syncNameMsgs(msgID)) : NAT*/[mk] ss[mk] pr[mk] pr/*SUCCESS*/

B.5 Teorema 5Não reconhece que um dado conjunto faz parte dos naturais, por ser umahipótese mais complexa. Exige a utilização de dois tipos de demonstradores.

[mk] cl[zm] /*lastSyncTS<+{destDevice|->(lastSyncTS(destDevice)<+

{origDevice|->max({lastSyncTS(destDevice)(origDevice),syncTSMsgs(msgID)})})} :

DEVICE +-> (DEVICE +-> NAT)*/[mk] xp[mk] ah({lastSyncTS(destDevice)(origDevice),

syncTSMsgs(msgID)} <: NAT)[zm] /*{lastSyncTS(destDevice)(origDevice),

syncTSMsgs(msgID)} <: NAT*/[mk] ah(lastSyncTS(destDevice)(origDevice) : NAT)[zm] /*lastSyncTS(destDevice)(origDevice) : NAT*/[mk] ah(lastSyncTS(destDevice) : DEVICE +-> NAT)

28

[zm] /*lastSyncTS(destDevice) : DEVICE +-> NAT*/[mk] pr[mk] pr[mk] pr[mk] ah(not({lastSyncTS(destDevice)(origDevice),

syncTSMsgs(msgID)}/\NATURAL = {}))[zm] /*not({lastSyncTS(destDevice)(origDevice),

syncTSMsgs(msgID)}/\NATURAL = {})*/[mk] pp(rp.0)[mk] pr/*SUCCESS*/

B.6 Teorema 6O Click’n’Prove não conseguiu processar a diferença entre dois conjuntos,sem ser ajudado com a indicação do tipo de um dado conjunto.

[mk] cl[zm] /*syncMsgsId<+{destDevice|->(syncMsgsId(destDevice)<+

{origDevice|->syncMsgsId(destDevice)(origDevice)-{msgID|->syncMsgsId(destDevice)(origDevice)(msgID)}})}

: DEVICE +-> (DEVICE --> (NAT +-> NAT))*/[mk] xp[mk] ah(syncMsgsId(destDevice) : DEVICE +-> (NAT +-> NAT))[zm] /*syncMsgsId(destDevice) : DEVICE +-> (NAT +-> NAT)*/[mk] pr[mk] pr/*SUCCESS*/

29