Máquinas de estados finitos são talvez um dos conceitos mais fundamentais e amplamente utilizados em programação. Máquinas de estado finito (KA) são usadas ativamente em muitos nichos aplicados. Em particular, em nichos como APCS e telecomunicações, com os quais era possível lidar, as naves espaciais são encontradas com um pouco menos de frequência do que em todas as etapas.
Portanto, neste artigo, tentaremos falar sobre naves espaciais, principalmente sobre máquinas hierárquicas de estados finitos e seus recursos avançados. E conte um pouco sobre o suporte a naves espaciais no
SObjectizer-5 , a estrutura “ator” para C ++. Um desses
dois que são abertos, gratuitos, multiplataforma e ainda estão vivos.
Mesmo se você não estiver interessado no SObjectizer, mas nunca ouviu falar de máquinas hierárquicas de estados finitos ou de quão úteis são os recursos avançados de uma espaçonave, como manipuladores de entrada / saída para estados ou histórico de status, você pode estar interessado em olhar sob o gato e leia pelo menos a primeira parte do artigo.
Palavras gerais sobre máquinas de estados finitos
Não tentaremos realizar um programa educacional completo no artigo sobre o tópico de
autômatos e uma variedade de
máquinas de estados finitos . O leitor precisa ter pelo menos um entendimento básico desses tipos de entidades.
Máquinas avançadas de estado finito e seus recursos
A nave espacial possui vários recursos "avançados" que aumentam bastante a usabilidade da nave espacial no programa. Vamos dar uma olhada rápida nesses recursos "avançados".
Isenção de responsabilidade: se o leitor estiver familiarizado com os diagramas de estado da UML, ele não encontrará nada de novo aqui.
Máquinas de estado hierárquico
Talvez a oportunidade mais importante e valiosa seja a organização de uma hierarquia / agrupamento de estados. Como é precisamente a capacidade de colocar estados um no outro que elimina a “explosão” do número de transições de estado para estado, à medida que a complexidade da espaçonave aumenta.
É mais difícil explicar isso em palavras do que mostrar pelo exemplo. Portanto, vamos imaginar que temos um infokiosk na tela em que uma mensagem de boas-vindas é exibida pela primeira vez. O usuário pode selecionar o item "Serviços" e ir para a seção para selecionar os serviços de que precisa. Ou ele pode selecionar o item "Conta pessoal" e ir para a seção sobre como trabalhar com seus dados e serviços pessoais. Ou ele pode selecionar a seção Ajuda. Até agora, tudo parece ser simples e pode ser representado pelo seguinte diagrama de estado (o mais simplificado possível):

Mas vamos tentar garantir que, clicando no botão "Cancelar", o usuário possa retornar de qualquer seção para a página inicial com uma mensagem de boas-vindas:

O esquema está ficando complicado, mas ainda está sob controle. No entanto, lembremos que na seção "Serviços", podemos ter várias outras subseções, por exemplo, "Serviços populares", "Novos serviços" e "Lista completa". E em cada uma dessas seções, você também precisa retornar à página inicial. Nossa espaçonave simples está se tornando cada vez mais difícil:

Mas isso está longe de tudo. Ainda não levamos em conta o botão "Voltar", pelo qual precisamos retornar à seção anterior. Vamos adicionar uma reação ao botão "Voltar" e ver o que temos:

Sim, agora vemos o caminho para a verdadeira diversão. Mas nem sequer consideramos as subseções nas seções "Minha conta" e "Ajuda" ... Se começarmos, então quase imediatamente nossa espaçonave simples, a princípio, se tornará algo inimaginável.
Aqui a aninhamento de estados vem em nosso auxílio. Vamos imaginar que temos apenas dois estados de nível superior: WelcomeScreen e UserSelection. Todas as nossas seções (por exemplo, "Serviços", "Minha conta" e "Ajuda") serão "aninhadas" no estado UserSelection. Você pode dizer que os estados ServicesScreen, ProfileScreen e HelpScreen serão filhos da UserSelection. E, como são filhos, herdarão a reação a alguns sinais do estado parental. Portanto, podemos definir a resposta ao botão Cancelar no UserSelection. Mas não precisamos determinar essa reação em todos os subestados subsidiários. O que torna nossa espaçonave mais concisa e compreensível:

Aqui você pode observar que a reação para "Cancelar" e "Voltar" foi definida em UserSelection. E essa reação ao botão Cancelar funciona para todos, sem exceção, sub-estados UserSelection (incluindo ainda outro sub-estado ServicesSelection composto). Mas no subestado ServicesSelection, a reação ao botão Voltar já é diferente - o retorno não está no WelcomScreen, mas no ServicesScreen.
As CAs que usam uma hierarquia / aninhamento de estados são chamadas de máquinas de estados finitos hierárquicos (ICA).
Reação à entrada / saída de / para o estado
Um recurso muito útil é a capacidade de atribuir uma resposta à entrada de um estado específico, bem como uma reação à saída de um estado. Portanto, no exemplo acima com um infokiosk, um manipulador pode ser desligado para inserir cada um dos estados, o que alterará o conteúdo da tela do infokiosk.
O exemplo anterior pode ser expandido um pouco. Suponha que tenhamos dois subestados no WelcomScreen: BrightWelcomScreen, no qual a tela será destacada normalmente, e DarkWelcomScreen, no qual o brilho da tela será reduzido. Podemos criar um manipulador de entrada DarkWelcomScreen que escurecerá a tela. E um manipulador de saída DarkWelcomScreen que restaurará o brilho normal.

Mudança automática de estado após um tempo definido
Às vezes, pode ser necessário limitar a permanência da espaçonave em um estado específico. Portanto, no exemplo acima, podemos limitar o tempo que nossa ICA permanece no estado BrightWelcomScreen a um minuto. Assim que o minuto expira, o ICA muda automaticamente para o estado DarkWelcomScreen.
História da nave espacial
Outra característica muito útil da ACI é a história do estado da espaçonave.
Vamos imaginar que temos algum tipo de ACI abstrata desse tipo:

Essa ACI pode ir de TopLevelState1 para TopLevelState2 e vice-versa. Mas, dentro do TopLevelState1, existem vários estados aninhados. Se o ICA simplesmente passar de TopLevelState2 para TopLevelState1, dois estados serão ativados imediatamente: TopLevelState1 e NestedState1. NestedState1 é ativado porque é o subestado inicial do estado TopLevelState1.
Agora imagine que nossa ICA alterou seu estado de NestedState1 para NestedState2. Dentro do NestedState2, o SubState InternalState1 foi ativado (já que é o subestado inicial do NestedState2). E do InternalState1, fomos para o InternalState2. Portanto, temos simultaneamente os seguintes estados ativos: TopLevelState1, NestedState2 e InternalState2. E aqui vamos para TopLevelState2 (ou seja, geralmente deixamos TopLevelState1).
Ativo se torna TopLevelState2. Depois disso, queremos retornar ao TopLevelState1. Está em TopLevelState1 e não em nenhum subestado específico em TopLevelState1.
Então, a partir do TopLevelState2, vamos para o TopLevelState1 e de onde chegamos?
Se TopLevelState1 não tiver histórico, chegaremos a TopLevelState1 e NestedState1 (já que NestedState1 é o subestado inicial de TopLevelState1). I.e. toda a história sobre as transições dentro do TopLevelState1, que ocorreu antes de deixar o TopLevelState2, foi completamente perdida.
Se TopLevelState1 tiver o chamado histórico superficial, ao retornar de TopLevelState2 para TopLevelState1, entramos em NestedState2 e InternalState1. Entramos no NestedState2 porque ele é registrado no histórico de status do TopLevelState1. E chegamos ao InternalState1 porque é o inicial do NestedState2. Acontece que no histórico superficial do TopLevelState1, as informações são armazenadas apenas sobre os subestados do primeiro nível. A história dos estados incorporados nesses subestados não é preservada.
Mas se TopLevelState1 tiver um histórico profundo, quando retornarmos de TopLevelState2 para TopLevelState1, entraremos em NestedState2 e InternalState2. Como em um histórico profundo, informações completas sobre subestados ativos são armazenadas, independentemente de sua profundidade.
Estados ortogonais
Até agora, examinamos a ACI em que apenas um dos subestados poderia estar ativo dentro do estado. Mas, às vezes, pode haver situações em que, em um estado específico da ACI, haja vários subestados ativos simultaneamente. Tais subestados são chamados estados ortogonais.
Um exemplo clássico que demonstra estados ortogonais é o familiar teclado do computador e seus modos NumLock, CapsLock e ScrollLock. Podemos dizer que o trabalho com NumLock / CapsLock / ScrollLock é descrito por subestados ortogonais dentro do estado Ativo:

Tudo o que você queria saber sobre máquinas de estados finitos, mas ...
Em geral, há um artigo fundamental sobre notação formal para diagramas de estado de David Harel:
Statecharts: A Visual Formalism For Complex Systems (1987) .
Lá, várias situações que podem ser encontradas ao trabalhar com máquinas de estados finitos são examinadas usando o exemplo de controle de um relógio eletrônico comum. Se alguém não leu, eu recomendo. Basicamente, tudo o que Harel descreveu passou para a notação UML. Mas quando você lê a descrição dos diagramas de estado na UML, nem sempre entende o que, por que e quando precisa. Mas no artigo de Harel, a apresentação vai de situações simples a situações mais complexas. E você está mais consciente de todo o poder que as máquinas de estados finitos escondem em si mesmas.
Máquinas de estado finito no SObjectizer
Além disso, falaremos sobre o SObjectizer e suas especificidades. Se você não entender os exemplos abaixo, talvez faça sentido aprender mais sobre o SObjectizer. Por exemplo, em nosso
artigo de revisão sobre o SObjecizer e em vários artigos subsequentes que apresentam leitores ao SObjectizer, passando de simples para complexos (
primeiro artigo,
segundo e
terceiro ).
Os agentes no SObjectizer são máquinas de estado
Os agentes no SObjectizer desde o início eram máquinas de estados com estados explícitos. Mesmo que o desenvolvedor do agente não tenha descrito nenhum de seus próprios estados em sua classe de agente, ele ainda possui um estado padrão, que é usado por padrão. Por exemplo, se um desenvolvedor fez um agente tão trivial:
class simple_demo final : public so_5::agent_t { public:
então ele pode nem suspeitar que, na realidade, todas as assinaturas que ele fez são feitas para o estado padrão. Mas se o desenvolvedor adicionar seus próprios estados ao agente, você já precisará pensar em assinar corretamente o agente no estado correto. Aqui, digamos, uma modificação incorreta simples (e, como sempre) do agente mostrada acima:
class simple_demo final : public so_5::agent_t {
Definimos dois manipuladores diferentes para o sinal how_are_you, cada um para seu próprio estado.
E o erro nessa modificação do agente simple_demo é que, estando em st_free ou st_busy, o agente não responderá ao sair, porque deixamos a assinatura encerrada no estado padrão, mas não fizemos as assinaturas correspondentes para st_free e st_busy. Uma maneira simples e óbvia de corrigir esse problema é adicionar as assinaturas apropriadas ao st_free e st_busy:
simple_demo(context_t ctx) : so_5::agent_t{std::move(ctx)} {
É verdade que esse método parece copiar e colar, o que não é bom. Você pode se livrar da copiar e colar inserindo um estado pai comum para st_free e st_busy:
class simple_demo final : public so_5::agent_t {
Por uma questão de justiça, deve-se acrescentar que inicialmente os agentes SObjectizer podiam ser apenas máquinas de estado simples. O suporte para naves espaciais hierárquicas apareceu relativamente recentemente, em janeiro de 2016.
Por que os agentes SObjectizer são máquinas de estado finito?
Essa pergunta tem uma resposta muito simples:
aconteceu que as raízes do SObjectizer crescem a partir do mundo dos sistemas de controle de processos, e as máquinas de estados finitos são usadas com muita frequência. Portanto, consideramos necessário que os agentes no SObjectizer também sejam máquinas de estado. Isso é muito conveniente se no aplicativo para o qual SObjectizer eles estão tentando aplicar, a CA é usada. E o estado padrão, que todos os agentes têm, nos permite não pensar em naves espaciais se o uso da espaçonave não for necessário.
Em princípio, se você olhar o próprio modelo de atores e os princípios nos quais esse modelo é construído:
- um ator é uma entidade com comportamento;
- atores respondem a mensagens recebidas;
- Após receber a mensagem, o ator pode:
- envie um certo número de mensagens para outros atores;
- criar uma série de novos atores;
- Defina um novo comportamento para o processamento de mensagens subseqüentes.
Pode-se encontrar uma forte semelhança entre espaçonaves simples e atores. Você poderia até dizer que os atores são simples máquinas de estados finitos.
Quais recursos de máquinas de estado avançadas o SObjectizer suporta?
Dos recursos acima das máquinas avançadas de estados finitos, o SObjectizer suporta tudo, exceto estados ortogonais. Outros itens, como estados aninhados, manipuladores de entrada / saída, restrições sobre o tempo gasto no estado, histórico para os estados, são suportados.
Com o apoio de estados ortogonais, a primeira vez não cresceu junto. Por um lado, a arquitetura interna do SObjectizer não pretendia oferecer suporte a vários estados independentes e simultaneamente ativos do agente. Por outro lado, há questões ideológicas sobre como um agente que possui estados ortogonais deve se comportar. O emaranhado dessas perguntas acabou sendo muito complicado, e a exaustão útil era pequena demais para resolver esse problema. Sim, e em nossa prática, ainda não houve situações em que estados ortogonais seriam necessários, mas seria impossível, por exemplo, dividir o trabalho entre vários agentes vinculados a um contexto de trabalho comum.
No entanto, se alguém precisar de um recurso como estados ortogonais e você tiver exemplos do mundo real de tarefas onde isso é necessário, então vamos conversar. Talvez, tendo exemplos concretos diante de nossos olhos, possamos adicionar esse recurso ao SObjectizer.
Como o suporte a recursos avançados do ICA é exibido no código
Nesta parte da história, tentaremos revisar rapidamente a API do SObjectizer-5 para trabalhar com a ICA. Sem se aprofundar nos detalhes, apenas para que o leitor tenha uma idéia do que é e como fica. Informações mais detalhadas, se você desejar, podem ser encontradas
na documentação oficial .
Estados aninhados
Para declarar um estado aninhado, você precisa passar a expressão initial_substate_of ou substate_of para o construtor do objeto state_t correspondente:
class demo : public so_5::agent_t { state_t st_parent{this};
Se o estado S possui vários subestados C1, C2, ..., Cn, um deles (e apenas um) deve ser marcado como inicial_substate_of. A violação desta regra é diagnosticada em tempo de execução.
A profundidade máxima de aninhamento de estado no SObjectizer-5 é limitada. Nas versões 5.5, esses são 16 níveis. A violação desta regra é diagnosticada em tempo de execução.
O truque mais importante com estados aninhados é que, quando um estado que possui estados aninhados é ativado, vários estados são ativados ao mesmo tempo. Suponha que exista um estado A que possua os subestados B e C e no subestado B existam os subestados D e E:

Quando o estado A é ativado, na verdade, três estados são ativados imediatamente: A, AB e ABD
O fato de vários estados poderem estar ativos ao mesmo tempo tem o efeito mais sério em duas coisas de arquivo. Primeiro, procure um manipulador para a próxima mensagem recebida. Portanto, no exemplo mostrado, o manipulador de mensagens será pesquisado primeiro no estado ABD. Se não houver um manipulador adequado, a pesquisa continuará no estado pai, ou seja, em AB E já machucou, se necessário, a pesquisa continuará no estado A.
Em segundo lugar, a presença de vários estados ativos afeta a ordem de chamada de manipuladores de entrada / saída para estados. Mas isso será discutido abaixo.
Manipuladores de E / S do estado
Para um estado, os manipuladores de estado de entrada e saída de estado podem ser especificados. Isso é feito usando os métodos state_t :: on_enter e state_t :: on_exit. Normalmente, esses métodos são chamados no método so_define_agent () (ou diretamente no construtor do agente, se o agente for trivial e a herança dele não for fornecida).
class demo : public so_5::agent_t { state_t st_free{this}; state_t st_busy{this}; ... void so_define_agent() override {
Provavelmente, o momento mais difícil com os manipuladores on_enter / on_exit é usá-los para estados aninhados. Vamos voltar ao exemplo com os estados A, B, C, D e E.

Suponha que cada estado tenha um manipulador on_enter e on_exit.
Deixe A. se tornar o estado atual do agente. os estados A, AB e ABD são ativados Durante a mudança de estado de um agente, A.on_enter, ABon_enter e ABDon_enter serão chamados. E nessa ordem.
Suponha que exista uma transição para o ABE, ABDon_exit e ABEon_enter serão chamados.
Se então colocarmos o agente no estado AC, ABEon_exit, ABon_exit, ACon_enter será chamado.
Se o agente, estando no estado AC, for cancelado o registro, imediatamente após a conclusão do método so_evt_finish (), os manipuladores ACon_exit e A.on_exit serão chamados.
Prazos
O limite de tempo para o agente permanecer em um estado específico é definido usando o método state_t :: time_limit. Assim como no on_enter / on_exit, os métodos time_limit geralmente são chamados onde o agente está configurado para funcionar dentro do SObjectizer:
class led_indicator : public so_5::agent_t { state_t inactive{this}; state_t active{this}; ... void so_define_agent() override {
Se o limite de tempo para o estado estiver definido, assim que o agente entrar nesse estado, o SObjectizer começará a contar o tempo gasto no estado. Se o agente sair do estado e retornar a esse estado novamente, a contagem regressiva será iniciada novamente.
Se limites de tempo forem definidos para estados incorporados, você precisará ter cuidado, porque truques curiosos são possíveis:
class demo : public so_5::agent_t {
Suponha que um agente entre no estado A. os estados A e C são ativados para A e C. Anteriormente, terminava no estado C e o agente passava para o estado D. Isso iniciará a contagem regressiva para permanecer no estado D. Mas a contagem regressiva continuará para permanecer no A! Como durante a transição de C para D, o agente continuou no estado A. E cinco segundos após a transição forçada de C para D, o agente passará para o estado B.
História para fortuna
Por padrão, os estados do agente não têm um histórico. Para ativar o histórico de salvar um estado, passe a constante shallow_history (o estado terá um histórico raso) ou deep_history (o estado terá um histórico profundo) para o construtor state_t. Por exemplo:
class demo : public so_5::agent_t { state_t A{this, shallow_history}; state_t B{this, deep_history}; ... };
A história dos estados é um tópico difícil, especialmente quando é usada uma profundidade decente de aninhamento de estados e os subestados têm sua própria história. Portanto, para obter informações mais completas sobre esse tópico, é melhor consultar
a documentação , experimentar. Bem, para nos perguntar se você não consegue descobrir;)
just_switch_to, transfer_to_state, suprimir
A classe state_t possui vários dos métodos mais usados já mostrados acima: event () para inscrever eventos em uma mensagem, on_enter () e on_exit () para definir manipuladores de entrada / saída, time_limit () para definir um limite para o tempo gasto em um estado.
Junto com esses métodos, ao trabalhar com o ICA, os seguintes métodos da classe state_t são muito úteis:
O método just_switch_to (), desenvolvido para o caso em que a única reação a uma mensagem recebida é transferir o agente para um novo estado. Você pode escrever:
some_state.just_switch_to<some_msg>(another_state);
em vez de:
some_state.event([this](mhood_t<some_msg>) { this >>= another_state; });
O método transfer_to_state () é muito útil quando temos alguma mensagem M processada da mesma maneira em dois ou mais estados S1, S2, ..., Sn. Mas, se estamos nos estados S2, ..., Sn, primeiro precisamos retornar a S1, e somente então o processamento M.
Se isso parecer complicado, talvez em um exemplo de código essa situação seja melhor compreendida:
class demo : public so_5::agent_t { state_t S1{this}, S2{this}, ..., Sn{this}; ... void actual_M_handler(mhood_t<M> cmd) {...} ... void so_define_agent() override { S1.event(&demo::actual_M_handler); ...
Mas, em vez de definir manipuladores de eventos muito semelhantes para S2, ..., Sn, use transfer_to_state:
class demo : public so_5::agent_t { state_t S1{this}, S2{this}, ..., Sn{this}; ... void actual_M_handler(mhood_t<M> cmd) {...} ... void so_define_agent() override { S1.event(&demo::actual_M_handler); ...
O método suppress () suprime uma pesquisa de manipulador de eventos para o subestado atual e todos os seus subestados pai. Suponha que tenhamos um estado pai A no qual std :: abort () é chamado na mensagem M. E existe um estado filho de B no qual M pode ser ignorado com segurança. Devemos determinar a reação a M no subestado B, porque, se não o fizermos, o manipulador de B será encontrado em A. Portanto, precisaremos escrever algo como:
void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.event([](mhood_t<M>) {});
O método suppress () permite que você escreva essa situação no código de maneira mais explícita e gráfica:
void so_define_agent() override { A.event([](mhood_t<M>) { std::abort(); }); ... B.suppress<M>();
Exemplo muito simples
Os exemplos padrão do SObjectizer v.5.5 incluem um exemplo simples,
piscando_led , que simula a operação de um indicador LED piscando. O diagrama de estado do agente deste exemplo é o seguinte:

E aqui está o código completo do agente deste exemplo:
class blinking_led final : public so_5::agent_t { state_t off{ this }, blinking{ this }, blink_on{ initial_substate_of{ blinking } }, blink_off{ substate_of{ blinking } }; public : struct turn_on_off : public so_5::signal_t {}; blinking_led( context_t ctx ) : so_5::agent_t{ ctx } { this >>= off; off.just_switch_to< turn_on_off >( blinking ); blinking.just_switch_to< turn_on_off >( off ); blink_on .on_enter( []{ std::cout << "ON" << std::endl; } ) .on_exit( []{ std::cout << "off" << std::endl; } ) .time_limit( std::chrono::milliseconds{1250}, blink_off ); blink_off .time_limit( std::chrono::milliseconds{750}, blink_on ); } };
Aqui, todo o trabalho real é feito dentro dos manipuladores de E / S para o subestado blink_on. Além disso, limites para a duração da permanência no trabalho dos subestados blink_on e blink_off.
Não é um exemplo muito simples
Os exemplos padrão do SObjectizer v.5.5 também incluem um exemplo muito mais complexo,
intercom_statechart , que imita o comportamento do painel do interfone. E o diagrama de estado do agente principal neste exemplo é mais ou menos assim:

Tudo é tão duro, porque essa imitação suporta não apenas ligar para um apartamento por número, mas também coisas como um código secreto exclusivo para cada apartamento, bem como um código de serviço especial. Esses códigos permitem abrir a fechadura da porta sem discar para qualquer lugar.
Ainda há coisas interessantes neste exemplo. Mas é muito grande para ser descrito em detalhes (mesmo um artigo separado pode não ser suficiente para isso). Portanto, se você estiver interessado em saber como as ICAs realmente complexas são exibidas no SObjectizer, você pode ver neste exemplo. E se algo não estiver claro, você pode nos fazer uma pergunta. Por exemplo, nos comentários deste artigo.
É possível não usar o suporte para naves espaciais integradas no SObjectizer-5?
Portanto, o SObjectizer-5 possui suporte interno para ICA com uma ampla gama de recursos suportados. Esse suporte é feito, é claro, para usá-lo. Em particular, os mecanismos de depuração do SObjectizer, como
rastreamento de entrega de mensagens , estão cientes do estado do agente e exibem o estado atual em suas respectivas mensagens de depuração.
No entanto, se o desenvolvedor não quiser, por algum motivo, usar as ferramentas internas do SObjectizer-5, ele poderá não fazer isso.
Por exemplo, você pode se recusar a usar o SObjectizer state_t e outros similares porque state_t é um objeto bastante pesado com std :: string interno, algumas funções std ::, vários contadores como std :: size_t, cinco ponteiros para vários objetos e alguma outra ninharia. Juntos, isso no Linux de 64 bits e no GCC-5.5, por exemplo, fornece 160 bytes por state_t (além do que pode ser alocado na memória dinâmica).
Se você precisar, digamos, de um milhão de agentes no aplicativo, cada um com 10 estados, a sobrecarga do SObjectizer state_t pode não ser aceitável. Nesse caso, você pode usar outro mecanismo para trabalhar com máquinas de estado, delegando manualmente o processamento de mensagens para esse mecanismo. Algo como:
class external_fsm_demo : public so_5::agent_t { some_fsm_type my_fsm_; ... void so_define_agent() override { so_subscribe_self() .event([this](mhood_t<msg_one> cmd) { my_fsm_.handle(*cmd); }) .event([this](mhood_t<msg_two> cmd) { my_fsm_.handle(*cmd); }) .event([this](mhood_t<msg_three> cmd) { my_fsm_.handle(*cmd); }); ... } ... };
Nesse caso, você está pagando pela eficiência aumentando a quantidade de trabalho manual e a falta de ajuda dos mecanismos de depuração do SObjectizer. Mas aqui cabe ao desenvolvedor decidir.
Conclusão
O artigo acabou sendo volumoso, muito mais do que o planejado originalmente. Obrigado a todos que leram para este lugar. Se um dos leitores considerar possível deixar seu feedback nos comentários do artigo, será ótimo.
Se algo permanecer incerto, faça perguntas, responderemos com prazer.
Além disso, aproveitando esta oportunidade, quero chamar a atenção daqueles que estão interessados no SObjectizer, que o trabalho começou na próxima versão do SObjectizer no quadro da ramificação 5.5. Brevemente sobre o que é considerado para implementação em 5.5.23, descrito aqui . Mais detalhadamente, mas em inglês, aqui . Você pode deixar sua opinião sobre qualquer um dos recursos propostos para implementação ou oferecer outra coisa. I.e.
existe uma oportunidade real de influenciar o desenvolvimento do SObjectizer. Além disso, após o lançamento da v.5.5.23, pode haver uma pausa no trabalho no SObjectizer e a próxima oportunidade de incluir algo útil no SObjectizer 2018 pode não estar lá.