Avançar para a área de conteúdo

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Todas as informações enviadas são seguras.

  • Fechar [x]

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

Ao clicar em Enviar, você concorda com os termos e condições do developerWorks.

Todas as informações enviadas são seguras.

  • Fechar [x]

Guia do Scala para desenvolvedores de Java atarefados: Melhorando a biblioteca Scitter

Ted Neward, Principal, ThoughtWorks, ThoughtWorks
Ted Neward photo
Ted Neward é consultor da ThoughtWorks, uma empresa de consultoria global, e diretor da Neward & Associates, onde atua como consultor, mentor, professor e apresentador do Java, .NET, XML Services e outras plataformas. Ele mora perto de Seattle, no estado de Washington.

Resumo:  É divertido falar sobre o Scala em termos abstratos, mas usá-lo de modo prático faz a diferença entre vê-lo como um "brinquedo" e usá-lo no trabalho. Neste artigo de sequência a sua apresentação ao Scitter, uma biblioteca-cliente do Scala para acessar o Twitter, o entusiasta do Scala Ted Neward oferece um conjunto de recursos mais interessante e útil para a biblioteca-cliente.

Visualizar mais conteúdo nesta série

Data:  02/Jun/2009
Nível:  Intermediário
Atividade:  2828 visualizações
Comentários:  


Bem-vindos de volta, fãs do Scala. No mês passado, conversamos sobre o Twitter, o site de micro-blogging que atualmente está despertando um tremendo interesse entre as pessoas que se socializam na Internet, e como seu API baseado em XML-/REST fazem dele um campo interessante para a investigação e exploração pelos desenvolvedores. Para esta finalidade, começamos a desvendar a estrutura básica do "Scitter", uma biblioteca Scala para acessar o Twitter.

Sobre esta série

Ted Neward leva você para um mergulho na linguagem de programação Scala. Nesta série developerWorks, você aprenderá do que se trata este alarde recente e verá algumas capacidades linguísticas do Scala em ação. O código Scala e o código Java™ serão exibidos lado a lado sempre que a comparação for relevante, mas (como você vai descobrir) muitas coisas no scala não têm correlação direta com nada que você encontra no Java — e é aí que mora grande parte do encanto do Scala! Afinal, se o Java pode fazer, por que se incomodar em aprender Scala?

Nós temos diversas metas para o Scitter:

  • Para tornar o acesso ao Twitter substancialmente mais fácil do que apenas abrir uma conexão HTTP e fazer o trabalho "manualmente"
  • Para ser tão facilmente acessível aos clientes Java
  • Para facilitar as simulações para testes

Não vamos necessariamente desvendar todo o Twitter API nesta seção, mas cobriremos alguns itens-chave, procurando facilitar para que outros terminem o trabalho quando esta biblioteca chegar a um repositório de controle de fonte pública.

A história até agora: Scitter 0.1

Vamos começar com um lembrete rápido de onde paramos:


Lista 1. Scitter v0.1

package com.tedneward.scitter
{
  import org.apache.commons.httpclient._, auth._, methods._, params._
  import scala.xml._

  /**
   * Status message type. This will typically be the most common message type
   * sent back from Twitter (usually in some kind of collection form). Note
   * that all optional elements in the Status type are represented by the
   * Scala Option[T] type, since that's what it's there for.
   */
  abstract class Status
  {
    /**
     * Nested User type. This could be combined with the top-level User type,
     * if we decide later that it's OK for this to have a boatload of optional
     * elements, including the most-recently-posted status update (which is a
     * tad circular).
     */
    abstract class User
    {
      val id : Long
      val name : String
      val screenName : String
      val description : String
      val location : String
      val profileImageUrl : String
      val url : String
      val protectedUpdates : Boolean
      val followersCount : Int
    }
    /**
     * Object wrapper for transforming (format) into User instances.
     */
    object User
    {
      /*
      def fromAtom(node : Node) : Status =
      {
      
      }
      */
      /*
      def fromRss(node : Node) : Status =
      {
      
      }
      */
      def fromXml(node : Node) : User =
      {
        new User {
          val id = (node \ "id").text.toLong
          val name = (node \ "name").text
          val screenName = (node \ "screen_name").text
          val description = (node \ "description").text
          val location = (node \ "location").text
          val profileImageUrl = (node \ "profile_image_url").text
          val url = (node \ "url").text
          val protectedUpdates = (node \ "protected").text.toBoolean
          val followersCount = (node \ "followers_count").text.toInt
        }
      }
    }
  
    val createdAt : String
    val id : Long
    val text : String
    val source : String
    val truncated : Boolean
    val inReplyToStatusId : Option[Long]
    val inReplyToUserId : Option[Long]
    val favorited : Boolean
    val user : User
  }
  /**
   * Object wrapper for transforming (format) into Status instances.
   */
  object Status
  {
    /*
    def fromAtom(node : Node) : Status =
    {
    
    }
    */
    /*
    def fromRss(node : Node) : Status =
    {
    
    }
    */
    def fromXml(node : Node) : Status =
    {
      new Status {
        val createdAt = (node \ "created_at").text
        val id = (node \ "id").text.toLong
        val text = (node \ "text").text
        val source = (node \ "source").text
        val truncated = (node \ "truncated").text.toBoolean
        val inReplyToStatusId =
          if ((node \ "in_reply_to_status_id").text != "")
            Some((node \"in_reply_to_status_id").text.toLong)
          else
            None
        val inReplyToUserId = 
          if ((node \ "in_reply_to_user_id").text != "")
            Some((node \"in_reply_to_user_id").text.toLong)
          else
            None
        val favorited = (node \ "favorited").text.toBoolean
        val user = User.fromXml((node \ "user")(0))
      }
    }
  }


  /**
   * Object for consuming "non-specific" Twitter feeds, such as the public timeline.
   * Use this to do non-authenticated requests of Twitter feeds.
   */
  object Scitter
  {
    /**
     * Ping the server to see if it's up and running.
     *
     * Twitter docs say:
     * test
     * Returns the string "ok" in the requested format with a 200 OK HTTP status code.
     * URL: http://twitter.com/help/test.format
     * Formats: xml, json
     * Method(s): GET
     */
    def test : Boolean =
    {
      val client = new HttpClient()

      val method = new GetMethod("http://twitter.com/help/test.xml")

      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))

      client.executeMethod(method)
      
      val statusLine = method.getStatusLine()
      statusLine.getStatusCode() == 200
    }
    /**
     * Query the public timeline for the most recent statuses.
     *
     * Twitter docs say:
     * public_timeline
     * Returns the 20 most recent statuses from non-protected users who have set
     * a custom user icon.  Does not require authentication.  Note that the
     * public timeline is cached for 60 seconds so requesting it more often than
     * that is a waste of resources.
     * URL: http://twitter.com/statuses/public_timeline.format
     * Formats: xml, json, rss, atom
     * Method(s): GET
     * API limit: Not applicable
     * Returns: list of status elements     
     */
    def publicTimeline : List[Status] =
    {
      import scala.collection.mutable.ListBuffer
    
      val client = new HttpClient()

      val method = new GetMethod("http://twitter.com/statuses/public_timeline.xml")

      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))

      client.executeMethod(method)
      
      val statusLine = method.getStatusLine()
      if (statusLine.getStatusCode() == 200)
      {
        val responseXML =
          XML.loadString(method.getResponseBodyAsString())

        val statusListBuffer = new ListBuffer[Status]

        for (n <- (responseXML \\ "status").elements)
          statusListBuffer += (Status.fromXml(n))
        
        statusListBuffer.toList
      }
      else
      {
        Nil
      }
    }
  }
  /**
   * Class for consuming "authenticated user" Twitter APIs. Each instance is
   * thus "tied" to a particular authenticated user on Twitter, and will
   * behave accordingly (according to the Twitter API documentation).
   */
  class Scitter(username : String, password : String)
  {
    /**
     * Verify the user credentials against Twitter.
     *
     * Twitter docs say:
     * verify_credentials
     * Returns an HTTP 200 OK response code and a representation of the
     * requesting user if authentication was successful; returns a 401 status
     * code and an error message if not.  Use this method to test if supplied
     * user credentials are valid.
     * URL: http://twitter.com/account/verify_credentials.format
     * Formats: xml, json
     * Method(s): GET
     */
    def verifyCredentials : Boolean =
    {
      val client = new HttpClient()

      val method = new GetMethod("http://twitter.com/help/test.xml")

      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))
        
      client.getParams().setAuthenticationPreemptive(true)
      val creds = new UsernamePasswordCredentials(username, password)
      client.getState().setCredentials(
        new AuthScope("twitter.com", 80, AuthScope.ANY_REALM), creds)

      client.executeMethod(method)
      
      val statusLine = method.getStatusLine()
      statusLine.getStatusCode() == 200
    }
  }
}

É um pouco longo, mas bastante fácil de destilar em alguns componentes básicos:

  • Classes de casos User e Status representam os tipos básicos que o Twitter envia de volta a seus clientes em resposta a uma chamada API, completa com métodos para construir e extrair de Representações XML.

  • Um objeto singleton do Scitter gerencia estas operações, que não requerem usuários autenticados.

  • Uma instância do Scitter (parametrizada por nome de usuário e senha) é usada para operações que requerem um usuário autenticado.

Sobre o Twitter API

Se você não tem familiaridade com o Twitter API, vale a pena gastar alguns minutos para checar a Wiki page do Twitter API para maiores detalhes. Os fundamentos básicos são simples parâmetros são passados como parte da query URL, as respostas podem ser em um dos quatro formatos (JSON, XML, ATOM ou RSS) e assim por diante mas, como com todos os APIs, o diabo mora nos detalhes, e assumir que os leitores estejam com o Twitter API aberto em um navegador em algum lugar enquanto leem este artigo facilita nosso foco nas partes do Scala da discussão.

Até agora nos dois tipos de Scitter, cobrimos somente os APIs teste, verifyCredentials e public_timeline. Embora eles ajudem a verificar se a parte básica do acesso HTTP (usando a biblioteca Apache HttpClient) funciona e se nossa forma básica de converter as respostas XML em Status objetos funciona, agora não conseguimos fazer nem a consulta básica de linha de tempo pública "o que meus amigos estão dizendo", nem demos os passos básicos para evitar os tipos de problemas de "repetir-se" no código base, e ainda nem começamos a ver maneiras de facilitar a simulação do acesso à rede para testes.

É óbvio que temos um longo caminho à frente neste episódio.

Conectando

A primeira coisa que me incomoda no código é que estou repetindo a sequência de operações para criar uma instância HttpClient, inicializá-la, parametrizá-la com os parâmetros de autenticação necessários e assim por diante, em cada um dos métodos do objeto e da classe Scitter. Quando há somente três métodos entre os dois, pode ser controlável, mas fica claro que não irá escalar, e existem muitos métodos ainda por fazer. Além disso, será realmente difícil retornar a esses métodos depois e introduzir algum tipo de simulação e/ou capacidade de teste local/offline. Então, vamos consertar isso.

O que estamos apresentando aqui não é realmente uma especificidade do Scala em si; é apenas o bom e simples tipo de raciocínio "Não Se Repita". Assim, vou começar com a abordagem básica orientada para OO: criar um método para ajudar a fazer o trabalho real:


Lista 2. Aplicando DRY ao código base

package com.tedneward.scitter
{
  // ...
  object Scitter
  {
    // ...
    private[scitter] def execute(url : String) =
    {
      val client = new HttpClient()
      val method = new GetMethod(url)
      
      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))
        
      client.executeMethod(method)
      
      (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
    }
  }
}

Concentre-se na sigla DRY

Para os que não leram Programador Pragmático ou faz tempo que o leram, DRY quer dizer "Don't Repeat Yourself" (Não Se Repita). Isso basicamente sugere que sempre que você estiver digitando os mesmos bits de código repetidamente, está se repetindo e finalmente vai se ver desejando que esses bits de código que fazem exatamente a mesma coisa estivessem juntos em um local (como um método) para que pudessem ser facilmente reparados, melhorados ou substituídos mais tarde. E se você ainda não leu Programados Pragmático até agora, que vergonha! Essa é sua lição de casa do mês.

Observe algumas coisas sobre isso: em primeiro lugar, estou retornando uma tupla do execute() método contendo o código do status e o corpo da resposta; esta é uma das partes poderosas de ter tuplas como uma parte integrada da linguagem, pois fica realmente fácil devolver o que de fato acaba sendo valores múltiplos de retorno de uma chamada simples do método. É claro que, no código Java, podemos fazer a mesma coisa criando um nível superior ou classe aninhada que contenha os elementos da tupla, mas isso exigirá uma quantidade enorme de código que é específico para este método particular. Ou poderíamos devolver um Map com String chaves e Object valores, mas aí perderíamos grandemente a segurança na digitação. As tuplas não são um recurso que altera o jogo, e sim apenas outro "item legal" que faz do Scala uma linguagem poderosa de usar.

Em razão de estar usando uma tupla, vou querer usar outra sintaxe do Scala para capturar ambos os resultados em variáveis locais, como esta versão reescrita do Scitter.test:


Lista 3. Esta é uma execução DRY?

package com.tedneward.scitter
{
  // ...
  object Scitter
  {
    /**
     * Ping the server to see if it's up and running.
     *
     * Twitter docs say:
     * test
     * Returns the string "ok" in the requested format with a 200 OK HTTP status code.
     * URL: http://twitter.com/help/test.format
     * Formats: xml, json
     * Method(s): GET
     */
    def test : Boolean =
    {
      val (statusCode, statusBody) =
        execute("http://twitter.com/statuses/public_timeline.xml")
      statusCode == 200
    }
  }
}

De fato, eu poderia facilmente remover statusBody totalmente e substituí-lo por _ porque nunca utilizo o segundo parâmetro (test não tem corpo de retorno), mas vou precisar do corpo para as outras chamadas, por isso vou deixá-lo aqui com finalidades de demonstração.

Note como execute() não vaza nenhum dos detalhes que lidam com a comunicação HTTP real — isso é o básico da Encapsulação 101. Isso vai facilitar a substituição de execute() por outra implementação mais tarde (o que faremos depois) ou a potencial otimização do código com a reutilização de um objeto HttpClient simples em vez de reinstanciar um novo a cada vez.

Otimização prematura

A propósito, a otimização seria útil aqui somente se HttpClient fizer algo incomum em seu construtor. Especificamente, não seria uma otimização útil tentar armazenar o HttpClient em cache apenas para salvar nas alocações do objeto — a lixeira, muito mal-falada na literatura de desempenho do Java por anos, é na verdade bastante boa para alocar e coletar objetos de vida curta, como o HttpClient objeto e seus similares neste caso particular. Mas, sobretudo, vou aplicar esta otimização somente se e quando descobrir que o execute() método seja de alguma forma um entrave ou um desperdício de recursos. Aja de acordo.

Depois, observe como o execute() método está no Scitter objeto? Isso quer dizer que poderei usá-lo a partir das diversas instâncias Scitter (pelo menos agora eu posso, até que faça algo dentro execute() que me impeça de fazê-lo) — é por isso que marquei execute() como private[scitter], o que significa que tudo dentro do com.tedneward.scitter pacote pode vê-lo.

(A propósito, se você ainda não o fez, execute os testes para garantir que tudo ainda esteja funcionando bem. Vou assumir que você está fazendo isso enquanto vemos o código, por isso se eu me esquecer de mencionar, isso não quer dizer que você pode esquecer de fazê-lo.)

A propósito, apoiando a classe Scitter vai requerer um nome de usuário e senha para acesso autenticado, por isso vou criar somente uma versão sobrecarregada do execute() método que usa dois adicionais Strings como parâmetros:


Lista 4. Uma versão ainda mais DRY

package com.tedneward.scitter
{
  // ...
  object Scitter
  {
    // ...
    private[scitter] def execute(url : String, username : String, password : String) =
    {
      val client = new HttpClient()
      val method = new GetMethod(url)
      
      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))
        
	  client.getParams().setAuthenticationPreemptive(true)
	  client.getState().setCredentials(
		new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
		  new UsernamePasswordCredentials(username, password))
      
      client.executeMethod(method)
      
      (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
    }
  }
}

De fato, considerando que os dois execute()s fazem praticamente a mesma coisa módulo os bits de autenticação, podemos reescrever o primeiro execute() inteiramente nos termos do segundo, lembrando que o Scala exige que sejamos explícitos quanto ao tipo de retorno do sobrecarregado execute():


Lista 5. DRY Radical

package com.tedneward.scitter
{
  // ...
  object Scitter
  {
    // ...
    private[scitter] def execute(url : String) : (Int, String) =
	  execute(url, "", "")
    private[scitter] def execute(url : String, username : String, password : String) =
    {
      val client = new HttpClient()
      val method = new GetMethod(url)
      
      method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER, 
        new DefaultHttpMethodRetryHandler(3, false))
        
      if ((username != "") && (password != ""))
      {
        client.getParams().setAuthenticationPreemptive(true)
        client.getState().setCredentials(
          new AuthScope("twitter.com", 80, AuthScope.ANY_REALM),
            new UsernamePasswordCredentials(username, password))
      }
      
      client.executeMethod(method)
      
      (method.getStatusLine().getStatusCode(), method.getResponseBodyAsString())
    }
  }
}

Até agora, tudo bem Executamos o DRY nas partes de comunicação do código Scitter, então vamos passar para o próximo item da lista: Vamos pegar uma lista dos tweets dos meus amigos.

Conectando (com amigos)

O Twitter API afirma que a friends_timeline chamada API "retorna os 20 status mais recentes publicados pelo usuário autenticado e pelos amigos desse usuário." (Ele também ressalta, para os que usam o Twitter diretamente no site do Twitter na Web, que "Este é o equivalente de '/home' na Web.") Este é um requisito bastante básico para qualquer Twitter API, então vamos adicioná-lo à Scitter classe; ele é adicionado à classe e não ao objeto porque, como indicam os documentos, isso é feito em nome do usuário em autenticação, que eu decidi que pertence à Scitter classe e não ao Scitter objeto.

Porém, agora somos arremessados como uma bola curva: A chamada friends_timeline aceita uma lista de "parâmetros opcionais", incluindo since_id, max_id, count e page para controlar os resultados encontrados. Essa vai ser uma operação complicada porque o Scala não suporta originalmente a ideia de "parâmetros opcionais" da forma como outras linguagens (como Groovy, JRuby ou JavaScript) fazem, mas vamos lidar primeiro com as coisas simples — vamos criar um método friendsTimeline que execute somente a chamada normal, não-parametrizada:


Lista 6. "Diga-me que empresa tu manténs..."

package com.tedneward.scitter
{
  class Scitter
  {
    def friendsTimeline : List[Status] =
    {
      val (statusCode, statusBody) =
       Scitter.execute("http://twitter.com/statuses/friends_timeline.xml",
                        username, password)

      if (statusCode == 200)
      {
        val responseXML = XML.loadString(statusBody)

        val statusListBuffer = new ListBuffer[Status]

        for (n <- (responseXML \\ "status").elements)
          statusListBuffer += (Status.fromXml(n))
        
        statusListBuffer.toList
      }
      else
      {
        Nil
      }
    }
  }
}

Até agora, tudo bem; o método correspondente para testar isso se parece assim:


Lista 7. "... e te direi quem és." (Miguel de Cervantes)

package com.tedneward.scitter.test
{
  class ScitterTests
  {
    // ...
	
    @Test def scitterFriendsTimeline =
    {
      val scitter = new Scitter(testUser, testPassword)
      val result = scitter.friendsTimeline
      assertTrue(result.length > 0)
    }
  }
}

Perfeito. Parece o método publicTimeline() do objeto Scitter e se comporta quase exatamente da mesma maneira também.

Futuros...

Como parte da redação deste artigo, perguntei aos autores de Programando no Scala Bill Venners, Martin Odersky e Lex Spoon qual eles achavam o melhor e/ou mais idiomático modo de lidar com os parâmetros opcionais, e na discussão que se seguiu, Martin mencionou que esta é uma área que eles estão pesquisando, mais possivelmente para a versão 2.8. Ainda não está claro se isso levará a forma de "parâmetros padrão", pelo menos não para mim, mas se esta é uma área que o intriga particularmente, esta é a hora de entrar nas conversas das listas de distribuição.

Ainda temos o problema dos parâmetros opcionais. Em razão do Scala não ter parâmetros opcionais como recurso de linguagem, a única opção , como primeira ideia, seria criar uma grande quantidade de métodos friendsTimeline() sobrecarregados, tomando alguns parâmetros fatorialmente governados.

Felizmente, existe um jeito mais fácil de fazer isso combinando dois recursos de linguagem Scala (um dos quais ainda não mencionei) de uma maneira interessante — classes de casos e "parâmetros repetidos" (consulte a Lista 8):


Lista 8. "Como eu te amo? ..."

package com.tedneward.scitter
{
  // ...
  
  abstract class OptionalParam
  case class Id(id : String) extends OptionalParam
  case class UserId(id : Long) extends OptionalParam
  case class Since(since_id : Long) extends OptionalParam
  case class Max(max_id : Long) extends OptionalParam
  case class Count(count : Int) extends OptionalParam
  case class Page(page : Int) extends OptionalParam
  
  class Scitter(username : String, password : String)
  {
    // ...
	
    def friendsTimeline(options : OptionalParam*) : List[Status] =
    {
      val optionsStr =
        new StringBuffer("http://twitter.com/statuses/friends_timeline.xml?")
      for (option <- options)
      {
        option match
        {
          case Since(since_id) =>
            optionsStr.append("since_id=" + since_id.toString() + "&")
          case Max(max_id) =>
            optionsStr.append("max_id=" + max_id.toString() + "&")
          case Count(count) =>
            optionsStr.append("count=" + count.toString() + "&")
          case Page(page) =>
            optionsStr.append("page=" + page.toString() + "&")
        }
      }
      
      val (statusCode, statusBody) =
        Scitter.execute(optionsStr.toString(), username, password)
      if (statusCode == 200)
      {
        val responseXML = XML.loadString(statusBody)

        val statusListBuffer = new ListBuffer[Status]

        for (n <- (responseXML \\ "status").elements)
          statusListBuffer += (Status.fromXml(n))
        
        statusListBuffer.toList
      }
      else
      {
        Nil
      }
    }
  }
}

Consulte o * tagging no final do parâmetro de opções? Isso indica que o parâmetro é na verdade uma sequência de parâmetros, muito parecida com a estruturavarargs do Java 5. Assim como com varargs, o número de parâmetros passados pode ser zero assim como tínhamos antes (embora iremos precisar voltar ao código de teste e adicionar um par de parênteses à friendsTimeline chamada agora, pois de outro modo o compilador não saberá se estamos tentando chamar o método sem nenhum parâmetro ou tentando usá-lo com finalidades de aplicação parcial ou algo semelhante); também podemos começar a passar estes tipos de caso, como a seguir:


Lista 9. "... Deixe-me contar as maneiras" (William Shakespeare)

package com.tedneward.scitter.test
{
  class ScitterTests
  {
    // ...
	
    @Test def scitterFriendsTimelineWithCount =
    {
      val scitter = new Scitter(testUser, testPassword)
      val result = scitter.friendsTimeline(Count(5))
      assertTrue(result.length == 5)
    }
  }
}

É claro que sempre existe a possibilidade de que um cliente sociopata possa passar em alguma sequência realmente bizarra de parâmetros, como friendsTimeline(Count(5), Count(6), Count(7)), mas nesse caso entregaremos a lista cegamente ao Twitter (e esperaremos que seu gerenciamento de erros seja forte o bastante para pegar somente o especificado). Claro que, se isso se tornar uma preocupação, não é difícil olhar a lista de parâmetros repetidos e pegar o último de cada tipo especificado antes de construir a URL enviada para o Twitter. Enquanto isso, previna-se.

Compatibilidade

Porém, isso realmente levanta uma questão interessante: Quão fácil é chamar este método com o código Java? Afinal, se uma das principais metas desta biblioteca é manter a compatibilidade com o código Java, então precisamos ter certeza de que o código Java não tenha que saltar muitos arcos para utilizá-lo.

Vamos começar lançando a classe Scitterem nosso bom amigo javap:


Lista 10. Ah, sim, código Java... Agora eu me lembro....

C:\>javap -classpath classes com.tedneward.scitter.Scitter
Compiled from "scitter.scala"
public class com.tedneward.scitter.Scitter extends java.lang.Object implements s
cala.ScalaObject{
    public com.tedneward.scitter.Scitter(java.lang.String, java.lang.String);
    public scala.List friendsTimeline(scala.Seq);
    public boolean verifyCredentials();
    public int $tag()       throws java.rmi.RemoteException;
}

Opa! Duas coisas saltam à minha mente aqui como preocupações. Primeiro, friendsTimeline() toma um scala.Seq como um parâmetro (que é o recurso de parâmetros repetidos que acabamos de usar). Segundo, o método friendsTimeline(), assim como com o método publicTimeline() do objeto Scitter (execute o javap nele para fazer uma checagem dupla se não acreditar em mim) retorna um scala.List de elementos. Quão utilizáveis são estes dois tipos de código Java?

A forma mais fácil de descobrir isso é escrever um pequeno conjunto de testes JUnit em código Java em vez de Scala, e é o que vamos fazer. Embora possamos testar a construção da instância do Scitter e chamar seu métodoverifyCredentials(), estes não são particularmente úteis — lembre-se, não estamos aqui (neste caso) para verificar a correção da Scitter classe, mas para ver como é fácil usar o item do código Java. Para este fim, vamos pular direto para escrever um teste que pegará a "linha do tempo dos amigos" — em outras palavras, queremos instanciar uma instância do Scitter e chamar seu friendsTimeline() método sem parâmetros.

Fazer isso é um pouco mais complicado, graças ao scala.Seq parâmetro que precisamos passar em — scala.Seq é uma característica do Scala que significa que ele é mapeado para o JVM subjacente, e por isso não podemos apenas instanciar um diretamente. Podemos tentar o parâmetro clássico Java null, mas fazer isso lança uma exceção no tempo de execução. O que precisamos é de uma scala.Seq classe que possamos instanciar facilmente com o código Java.

Acontece que encontramos um exatamente no mesmo mutable.ListBuffer tipo que usamos dentro da própria implementação do Scitter.


Lista 11. E agora lembramos porque eu gosto do Scala....

package com.tedneward.scitter.test;

import org.junit.*;
import com.tedneward.scitter.*;

public class JavaScitterTests
{
  public static final String testUser = "TESTUSER";
  public static final String testPassword = "TESTPASSWORD";
  
  @Test public void getFriendsStatuses()
  {
    Scitter scitter = new Scitter(testUser, testPassword);
    if (scitter.verifyCredentials())
    {
      scala.List statuses =
        scitter.friendsTimeline(new scala.collection.mutable.ListBuffer());
      Assert.assertTrue(statuses.length() > 0);
    }
    else
      Assert.assertTrue(false);
  }
}

Usar o scala.List retornado não é problema porque podemos apenas tratá-lo como faríamos com qualquer outra Collection classe (embora sintamos falta das facilidades das Collections API porque os métodos da Lista baseados no Scala assumem que você estará interagindo com eles a partir do scala),, e então navegar pelos resultados não é tão difícil, mas lembra um pouco a "velha escola" do código Java (em torno de 1995):


Lista 12. 1995 chamado; eles querem o Vector de volta...

package com.tedneward.scitter.test;

import org.junit.*;
import com.tedneward.scitter.*;

public class JavaScitterTests
{
  public static final String testUser = "TESTUSER";
  public static final String testPassword = "TESTPASSWORD";

  @Test public void getFriendsStatuses()
  {
    Scitter scitter = new Scitter(testUser, testPassword);
    if (scitter.verifyCredentials())
    {
      scala.List statuses =
        scitter.friendsTimeline(new scala.collection.mutable.ListBuffer());
      Assert.assertTrue(statuses.length() > 0);
      
      for (int i=0; i<statuses.length(); i++)
      {
        Status stat = (Status)statuses.apply(i);
        System.out.println(stat.user().screenName() + " said " + stat.text());
      }
    }
    else
      Assert.assertTrue(false);
  }
}

O que nos leva para a próxima parte, a de passar os parâmetros para dentro do friendsTimeline() método. Infelizmente, o tipo ListBuffer não toma uma coleção como parâmetro construtor, e por isso temos que construir uma lista de parâmetros e depois passar a coleção pela chamada do método;é tedioso, mas não excessivamente trabalhoso:


Lista 13. Posso voltar para o Scala agora, por favor?

package com.tedneward.scitter.test;

import org.junit.*;
import com.tedneward.scitter.*;

public class JavaScitterTests
{
  public static final String testUser = "TESTUSER";
  public static final String testPassword = "TESTPASSWORD";
  
  // ...

  @Test public void getFriendsStatusesWithCount()
  {
    Scitter scitter = new Scitter(testUser, testPassword);
    if (scitter.verifyCredentials())
    {
      scala.collection.mutable.ListBuffer params =
        new scala.collection.mutable.ListBuffer();
      params.$plus$eq(new Count(5));
      
      scala.List statuses = scitter.friendsTimeline(params);

      Assert.assertTrue(statuses.length() > 0);
      Assert.assertTrue(statuses.length() == 5);
      
      for (int i=0; i<statuses.length(); i++)
      {
        Status stat = (Status)statuses.apply(i);
        System.out.println(stat.user().screenName() + " said " + stat.text());
      }
    }
    else
      Assert.assertTrue(false);
  }
}

Assim, embora a versão Java seja um pouco mais verborrágica que sua equivalente no Scala, até agora ainda é bastante direto chamar a biblioteca do Scitter a partir de qualquer cliente Java que queira utilizá-la. Excelente.

Conclusão

Fica claro que ainda temos um longo caminho a percorrer com o Scitter, mas está começando a realmente tomar forma e, até agora, está sendo muito bom. Conseguimos executar o DRY nas partes de comunicação da biblioteca do Scitter e incorporar os parâmetros opcionais que o Twitter pede em algumas chamadas API diferentes que ele oferece — e, até agora, os clientes Java não ficarão desanimados demais com a API que estamos apresentando. Reconheço que essa não é uma API tão limpa quanto a que o Scala pode consumir naturalmente, mas se os desenvolvedores de Java quiserem usar a biblioteca do Scitter, eles não precisam fazer muita coisa para começar.

A biblioteca Scitter ainda carrega uma qualidade de "objeto", mas estamos começando a ver alguns recursos mais funcionais do Scala abrindo caminho para o sistema. Ao continuarmos a construção da biblioteca, cada vez mais destes recursos começarão a se introduzir lentamente, em qualquer local em que ajudem a tornar o código mais conciso e claro. E é assim que deve ser.

Por enquanto, é hora de nos separarmos enquanto eu faço uma breve pausa. Quando eu retornar, acrescentarei à biblioteca algum suporte para testes offline e também a habilidade de atualizar o status de um usuário. Até lá, fãs do Scala, lembrem-se: É sempre melhor ser funcional do que disfuncional. (Desculpem-me. Eu adoro demais essa piada.)


Recursos

Aprender

Obter produtos e tecnologias

Discutir

Sobre o autor

Ted Neward photo

Ted Neward é consultor da ThoughtWorks, uma empresa de consultoria global, e diretor da Neward & Associates, onde atua como consultor, mentor, professor e apresentador do Java, .NET, XML Services e outras plataformas. Ele mora perto de Seattle, no estado de Washington.

Ajuda para Relatar Abuso

Relatar abuso

Obrigado. Esta entrada foi sinalizada para atenção do moderador.


Ajuda para Relatar Abuso

Relatar abuso

Falha no envio do Relatório de abuso. Tente novamente mais tarde.


developerWorks: Registre-se


Precisa de um ID IBM?
Esqueceu seu ID IBM?


Esqueceu sua senha?
Alterar sua senha

Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Na primeira vez que você efetua sign in no developerWorks, um perfil é criado para você. Informações selecionadas do seu perfil developerWorks são exibidas ao público, mas você pode editá-las a qualquer momento. Seu primeiro nome, sobrenome (a menos que escolha ocultá-los), e seu nome de exibição acompanharão o conteúdo que postar.

Selecione seu nome de exibição

Ao se conectar ao developerWorks pela primeira vez, é criado um perfil para você e é necessário selecionar um nome de exibição. O nome de exibição acompanhará o conteúdo que você postar no developerWorks.

Escolha um nome de exibição de 3 - 31 caracteres. Seu nome de exibição deve ser exclusivo na comunidade do developerWorks e não deve ser o seu endereço de email por motivo de privacidade.

(Deve possuir de 3 a 31 caracteres.)


Ao clicar em Enviar, você concorda com os termos de uso do developerWorks.

 


Classificar este artigo

Comentários

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java, Software livre
ArticleID=404864
ArticleTitle=Guia do Scala para desenvolvedores de Java atarefados: Melhorando a biblioteca Scitter
publish-date=06022009
author1-email=ted@tedneward.com
author1-email-cc=jaloi@us.ibm.com

Conheça a IBM da sua cidade

Virtual Branch Office Brasil

A IBM está mais perto do que você imagina!


Tags

Help
Use o campo de pesquisa para encontrar todos os tipos de conteúdo no My developerWorks com essa tag.

Use a barra de rolagem para ver mais ou menos tags.

Tags populares mostra as principais tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Minhas tags mostra suas tags para esta zona de conteúdo em particular (por exemplo, Java technology, Linux, WebSphere).

Use o campo de pesquisa para localizar todos os tipos de conteúdo no Meu developerWorks com essa tag. Tags populares mostra as tags principais para essa zona de conteúdo particular (por exemplo, tecnologia Java, Linux, WebSphere). My tags shows your tags for this particular content zone (for example, Java technology, Linux, WebSphere). Minhas tags mostra as suas tags para essa zona de conteúdo em particular (por exemplo, tecnologia Java, Linux, WebSphere).