2009
12.16

Ogólnie o 70-562

Celem egzaminu numer 70-562 jest potwierdzenie kompetencji w zakresie programowania aplikacji internetowych za pomocą ASP.NET na platformie .NET 3.5. Jak czytamy na stronie poświęconej egzaminowi profil idealnego kandydata do zdania tego egzaminu to osoba pracująca w średnich-dużych zespołach z dwu-, trzyletnim doświadczeniem w dziedzinie tworzenia aplikacji webowych.

Prócz tego egzamin obejmuje wszystkie zagadnienia dotykające tematyki webaplikacji, między innymi:

  • Dostęp do danych za pomocą ADO.NET
  • Webserwisy
  • WCF
  • AJAX i Javascript
  • Konfiguracja IIS.

Zakres jest dość rozległy, ale nadal obraca się wokół pisania aplikacji internetowych i w tym kontekście sprawdza wiedzę z dodatkowych dziedzin.

Oczywiście – tak jak w przypadku innych egzaminów – samo kilkuletnie doświadczenie programistyczne nie gwarantuje pozytywnego złożenia egzaminu. Potrzebne jest jeszcze solidne powtórzenie swojej wiedzy i nauczenie się pewnych rzeczy na pamięć (co w codziennej pracy z powodzeniem jest zastępowane choćby przez Intellisense). Jak zatem dobrze przygotować się do złożenia egzaminu?

70-562 – jak zdać?

Dobrą pomocą do powtórzenia materiału jest na przykład książka: MCTS Self-Paced Training Kit (Exam 70-562): Microsoft .NET Framework 3.5-ASP.NET Application Development. Rozdział po rozdziale omawia  poszczególne zagadnienia, szczególnie dużo miejsca poświęcając ADO.NET. Trzeba zaznaczyć, że nie jest to książka do nauki programowania w ASP.NET  – można ją raczej nazwać repetytorium. Na uwagę zasługują ramki „Exam tip” z sugestiami, na co szczególnie zwrócić uwagę podczas egzaminu.

Książka na pierwszy rzut oka przeraża objętością – jest przynajmniej tak duża jak Self-Paced Training Kit dla egzaminu 70-536. Pamiętajmy jednak, że przykładowe kody źródłowe pisane są podwójnie (w C# i w VB.NET), a sporą cześć zajmują powtórki rozdziałów i przykładowe scenariusze.

Zasadniczo są dwa sposoby używania książki. Można próbować czytać ją tzw. ciurkiem i… po mniej więcej pół godziny odpłynąć do krainy Morfeusza. Nie polecam.

Drugi sposób – znacznie bardziej efektywny – to czytanie książki z otwartym Visual Studio i wypróbowywanie konstrukcji, o których właśnie czytasz. Oczywiście – pewnie większość z nich już kiedyś używałeś, ale być może nie zauważyłeś pewnych detali, które mogą zaważyć na tym, jak odpowiesz na pytania.

Olbrzymią wartością dodaną jest płyta z przykładowymi pytaniami testowymi. Jest 125 pytań podsumowujących poszczególne lekcje i 200 pytań praktycznych przypominających te, które będą na egzaminie.

W sieci można też kupić zestawy przykładowych pytań na MeasureUp lub na SelfTestSoftware, które także przypominają pytania na prawdziwym egzaminie i pozwalają ocenić stan przygotowania.

Tak na marginesie: w komentarzach do wpisu o egzaminie 70-536 użytkownik Matol napisał, że można kupić prawdziwe (lub zbliżone do prawdziwych) pytania z egzaminów albo nawet dostać je za darmo, w związku z czym – jego zdaniem – egzaminy te nie sprawdzają jakiejkolwiek rzeczywistej wiedzy. Z takim stwierdzeniem nie mogę się zgodzić (z wielu powodów). Jeśli natomiast Matol miał na myśli, że przez dumpy z pytań egzaminacyjnych wartość egzaminów się obniża, to owszem, skłonny jestem przyznać rację. Ale jednocześnie można powiedzieć, że również maturę i pracę magisterską można kupić (i także deprecjonuje to wartość tych egzaminów), a jednak nikt nie wyobraża sobie nie zrobić matury albo nie złożyć egzaminu magisterskiego. Tym niemniej Matol poczynił ciekawą uwagę i odniosę się do niej w innym wpisie.

Wracając do pytań testowych – oczywiście uczenie się odpowiedzi na pamięć nie ma najmniejszego sensu. Ale wykonywanie testów i sprawdzanie swoich odpowiedzi pozwala szybko zidentyfikować te obszary, w których nasza wiedza wymaga ulepszenia.

Ten model nauki sprawdził się w moim przypadku. Nie korzystałem z dumpów i nie mogę się pochwalić wynikiem 1000/1000, ale z powodzeniem zdałem egzamin. Oczywiście z powodu NDA nie mogę ujawniać treści egzaminu, ale zaskoczyła mnie minimalna liczba pytań dotycząca LINQ. Pozostałe proporcje były „w normie”.

Kilka podstawowych informacji dla osób, które jeszcze nie zdawały: egzamin pisze się w centrum szkoleniowym, które może organizować certyfikację Microsoft (aktualna lista ośrodków) za pomocą programu testującego na komputerze. Wynik zna się od razu po egzaminie. Do zaliczenia potrzeba 700 punktów na 1000 możliwych.

70-562 – zdałem i co dalej?

Jeśli planujesz przystąpić do egzaminu 70-562 raczej wątpię, że Twój wybór jest przypadkowy. Egzamin ten jest zwykle wybierany jako drugi, po zdaniu 70-536 (przeczytaj, jak przygotować się do egzaminu 70-536). Zdanie dwóch tych dwóch egzaminów skutkuje przyznaniem certyfikatu: Microsoft Certfified Technology Specialist  – .NET Framework 3.5, ASP.NET Application. Jeśli zdałeś te dwa egzaminy to jesteś wśród 59 osób w Polsce (dane na 8 grudnia 2009 – możesz zajrzeć do oficjalnej statystyki). Brzmi nieźle, prawda? ;)

Jeśli chcesz uzyskać wyższy poziom certyfikacji  – Microsoft Professional Developer (MPD) w dziedzinie ASP.NET na platformie .NET 3.5 i znaleźć się wśród 20 osób w Polsce posiadających ten certyfikat, będziesz zainteresowany zdaniem egzaminu 70-564: Pro: Designing and Developing ASP.NET Applications Using the Microsoft .NET Framework 3.5.

Podsumowanie

Na koniec krótkie podsumowanie:

  1. Egzamin 70-562 jest dla programistów ASP.NE T z kilkuletnim doświadczeniem.
  2. Istnieje sporo materiałów do nauki do egzaminu, w tym książka z MS-Press typu Self-Paced.
  3. Przydatne jest wykonywanie opisywanych ćwiczeń (pisanie kodu), tak żeby zaznajomić się z treścią w sposób praktyczny.
  4. Pomocne jest rozwiązywanie przykładowych testów i wyciąganie wniosków ze swoich błędnych odpowiedzi.
  5. Zdanie dwóch egzaminów 70-536 i 70-562 skutkuje uzyskaniem certyfikatu Microsoft Certified Specialist.

Powodzenia!

2009
11.30

W ASP.NET 2.0 wprowadzono interesującą i wydajną funkcjonalność – Web Parts. Czym są Web Parts? W skrócie: są to pewne zamknięte komponenty aplikacji internetowej, które są tworzone i zarządzane w jednolity sposób. Jedną z najbardziej użytecznych cech web partów (czasami będę odmieniać przez przypadki, ponieważ nie udało mi się znaleźć żadnego dobrego polskiego odpowiednika) jest to, że mogą być one dodawane, usuwane i edytowane nie tylko przez programistów, ale też przez użytkowników Twojej aplikacji internetowej. Web parts w akcji możesz zobaczyć np. na stronie: igoogle.com.

Ten wpis nie będzie o tworzeniu web partów – chcę skupić się na pewnym małym wycinku tematu – mianowicie na tworzeniu połączeń między web partami.

Po co łączyć web parts

Przypuśćmy, że tworzysz aplikację webową, która wyświetla szczegóły na temat europejskich miast. Chciałbyś zorganizować te stroną w taki sposób, żeby korzystała z web parts. W ten sposób umożliwiłbyś użytkownikowi samodzielne zarządzanie wyglądem strony. Załóżmy, że w jednym web parcie użytkownik będzie wpisywał (bądź wybierał z listy) nazwę miasta. W innych web partach pojawią się wówczas informacje takie jak:

  • liczba ludności miasta,
  • slideshow ze zdjęciami z miasta,
  • aktualna prognoza pogody, itd.

Aby uzyskać opisaną funkcjonalność, jako programista, musisz ustalić połączenie między web partami. Można to zrobić na dwa sposoby – 1) ustalić statyczne połączenie lub 2) umożliwić użytkownikom tworzenie dynamicznych powiązań. Omówię pierwszy z nich.

Statyczne powiązanie web partów

Statyczne powiązanie polega na wskazaniu jednego web parta jako dostawcy (provider) i jednego bądź więcej odbiorców (consumer).

Uwaga: dla ułatwienia jako web partów będziemy używać własnych kontrolek (user controls).

Stworzenie dostawcy polega na dodaniu do gotowej kontrolki metody, która będzie zwracała pewną wartość. W naszym przykładzie chcemy, aby była to nazwa miasta pochodząca z rozwijalnej listy. Oto, jak mógłby wyglądać przykładowy kod takiej kontrolki:

public partial class CityChooser : System.Web.UI.UserControl
{

    string _cityName = CityData.GetData().First().Name;

    [ConnectionProvider("My web parts connection", "GetCityName")]
    public string GetCityName()
    {
        return _cityName;
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (IsPostBack)
        {
            string userCity = DropDownList1.SelectedValue;
            _cityName = CityData.GetData().First(city => city.Name == userCity).Name;
        }

    }
}

Obiekt CityData zawiera po prostu listę miast w postaci struktur zawierających wszystkie potrzebne dane (np. pole Name czy Population). Dokładny wygląd tej klasy nie jest w tej chwili istotny. Najistotniejsza jest metoda:

[ConnectionProvider("My web parts connection")]
public string GetCityName()
{
    return _cityName;
}

Metoda ta (ważne – publiczna!) zwraca po prostu nazwę miasta wybranego z listy rozwijalnej. Atrybut ConnectionProvider określa, że jest to nasz dostawca danych dla innych web partów (konsumentów). Tu używamy z jednym parametrem (”przyjazną” nazwą połączenia), ale jest też przeciążona wersja z dwoma typu string. Wówczas pierwszy określa “przyjazną” nazwę naszego połączenia, a drugi definiuje unikalną nazwę naszego punktu połączenia. Można pominąć drugi parametr – wówczas nasz punkt będzie się nazywał default (oczywiście, gdybyśmy chcieli zdefiniować drugi ConnectionProvider, to wówczas trzeba już użyć drugiego parametru – nazwa default byłaby już zajęta).

Mamy zatem zdefiniowanego dostawcę naszych danych. Czas na konsumentów.

Stworzenie konsumenta polega na dodaniu do kontrolki będącej web partem metody (znów publicznej), która będzie przyjmować parametr – tego samego typu, jaki jest zwracany przez naszą metodę-dostawcę. Metodą tą należy opatrzyć atrybutem ConnectionConsumer. Całość mogłaby wyglądać następująco:

public partial class PopulationDisplayer : System.Web.UI.UserControl
{
    [ConnectionConsumer("My consumer")]
    public void ShowPopulation(string city)
    {
        lblCityPop.Text = CityData.GetData().First(c => c.Name == city).Population.ToString();
    }
}

I znów najważniejszy jest atrybut ConnectionConsumer. Parametry mają takie samo znaczenie jak w poprzednim przypadku.

Teraz, gdy mamy już zdefiniowanego dostawcę i co najmniej jednego odbiorcę, możemy ustalić połączenie. W tym celu musimy stworzyć stronę, na której będą działać nasze web party. Dodajemy oczywiście kontrolkę WebPartManager oraz WebPartZone, w której w ZoneTemplate umieszczamy nasze web party. Kod kontrolki WebPartZone może wyglądać następująco:

<asp:WebPartZone ID="WebPartZone3" runat="server" Style="width: 650px; height: auto;">
    <ZoneTemplate>
        <uc2:CityChooser ID="Provider" runat="server" />
        <uc3:PopulationDisplayer ID="Consumer" runat="server" />
    </ZoneTemplate>
</asp:WebPartZone>

Samo powiązanie definiujemy natomiast w kontrolce WebPartManager w części StaticConnections:

<asp:WebPartManager ID="WebPartManager1" runat="server">
    <StaticConnections>
        <asp:WebPartConnection
            ID="CityToPopulation"
            ProviderID="Provider"
            ConsumerID="Consumer">
        </asp:WebPartConnection>
    </StaticConnections>
</asp:WebPartManager>

I tak oto, gdy w jednym web parcie z listy wybierzemy miasto, to w drugim zobaczymy liczbę ludności tego miasta:

Powiązanie Web Parts

Powiązanie Web Parts

Gdybyśmy w atrybutach definiujących naszego dostawcę i konsumenta podali unikalne nazwy dla naszych punktów, np:

[ConnectionProvider("My web parts connection", "ProvideData")]
[ConnectionConsumer("My consumer", "ConsumeData")]

wówczas kod kontrolki WebPartManager wyglądałby nieco inaczej:

    <asp:WebPartManager ID="WebPartManager1" runat="server">
        <StaticConnections>
            <asp:WebPartConnection
            ID="CityToPopulation"
            ProviderID="Provider"
            ProviderConnectionPointID="ProvideData"
            ConsumerID="Consumer"
            ConsumerConnectionPointID="ConsumeData">
            </asp:WebPartConnection>
        </StaticConnections>
    </asp:WebPartManager>

Musielibyśmy wówczas określić, że chodzi nam o konkretne metody za pomocą właściwości: ProviderConnectionPointID i ConsumerConnectionPointID.

Gdybyśmy chcieli użyć większej ilości konsumentów, musielibyśmy zdefiniować odpowiednie WebPartConnection dla każdej pary web partów.

W taki sposób można łatwo definiować statyczne połączenia. O tym, jak umożliwić użytkownikom strony tworzenie dynamicznych połączeń, będzie następny (krótki) wpis.

Tym razem załączam kod źródłowy przykładowego rozwiązania – wersja dla Visual Studio 2008. Żeby korzystać z web partów, trzeba mieć SQL Server wersję Express, ponieważ web parts korzystają z bazy danych (App_Data/ASPNETDB.MDF), żeby zapamiętywać preferencje użytkownika. Paczka z kodem nie zawiera tego pliku – trzeba go sobie stworzyć samemu (np. przy pomocy narzędzia ASP.NET Configuration).

vssolutionWebParts static connections – kod C# (6,29 KB)

2009
05.05

W Internecie pojawił się film ze spotkania Steve’a Ballmera z polskimi społecznościami IT.

Polecam także stronę ze zdjęciami ze spotkania. Fotografie są wyświetlane za pomocą rewelacyjnego programu – Photosynth – opracowanego w Microsoft Live Labs.

2009
04.27

Ostatnio przy implementacji aplikacji dla platformy Mediaroom, napotkałem na problem ustalania podzbioru pewnego danego zbioru napisów, który to podzbiór miałby zawierać wszystkie stringi rozpoczynające się ustalonym prefiksem. Jak nietrudno się domyśleć, procedura potrzebna mi była do implementacji listy podpowiedzi – użytkownik rozpoczyna wprowadzanie tekstu, a na ekranie pojawia mu się lista podpowiedzi – scenariusz szeroko stosowany we współczesnym webie. W aplikacjach dla telewizji ma on jednak o wiele większe znaczenie, bo możliwości wprowadzania tekstu za pomocą pilota są raczej ograniczone (stosuje się metodę triple-tap znaną z komórek).

Problem przedstawia się zatem następująco: mając zadaną tablicę ze stringami (lista wszystkich możliwych sugestii), dla określonego prefiksu (ciągu znaków), podać listę tych stringów z tablicy wejściowej, które rozpoczynają się od prefiksu.

Trywialne rozwiązanie:

//dane wejściowe
String[] input = strings.ToArray();

//prefiks, dla którego szukamy podzbioru
String prefix = "ab";

String[] subset = Array.FindAll<String>(
    input,
    new Predicate<string>(s => s.StartsWith(prefix))
    );

ma jedną zaletę – zajmuje mało kodu. Niestety poza tym ma same wady:

  1. Czas ustalenia interesującego nas podzbioru zależy liniowo od wielkości danych wejściowych (liczby stringów w tablicy). Przy, powiedzmy, 50 tysiącach stringów użytkownik zauważalnie długo czeka na podpowiedź.
  2. Łatwo zauważyć, że dla każdej kolejnej wpisanej litery prefiksu, zawężamy wynik poszukiwań – dla n-tej litery prefiksu wynikiem jest podzbiór uzyskany w poprzednim kroku, dla n-1 litery. Trywialna metoda nie korzysta jednak z tej obserwacji i dla każdej litery proces poszukiwania zaczynany jest na nowo.

Z pomocą przychodzą drzewa trie (czyt. traj). Trie jest drzewem poszukiwań, którego wierzchołki są oznaczone kolejnymi prefiksami słów z listy. Prócz prefiksu wierzchołek zawiera informacje o słowach z listy, które “pasują” do tego prefiksu. Oczywiście w celach optymalizacji, w drzewie nie są przechowywane same podzbiory wyjściowego zbioru, tylko indeks początkowy i końcowy danego podzbioru.

Przykład:

Trie - przykład

Trie - przykład

Powyższe drzewo trie zawiera dane o prefiksach dla następującej listy słów:

  • AR
  • AS
  • B
  • DO
  • ELA
  • ELF
  • EM

Żeby otrzymać informację o podzbiorze stringów zaczynających się od prefiksu “EL” należy przejść od korzenia przez węzeł “E” aż do węzła “L” – otrzymujemy indeksy 4-5, czyli słowa “ELA” i “ELF”. Warto zauważyć, że koszt wyszukania interesującego nas podzbioru nie zależy teraz od wielkości danych wejściowych.

Zanim przystąpimy do napisania funkcji budującej drzewo, zdefiniujmy najpierw dane pomocnicze: strukturę Prefix, i klasę TreeNode<T>.

Prefix:

public struct Prefix
{
    int _start;
    public int Start
    {
        get { return _start; }
        set { _start = value; }
    }

    int _end;
    public int End
    {
        get { return _end; }
        set { _end = value; }
    }

    char _letter;
    public char Letter
    {
        get { return _letter; }
        set { _letter = value; }
    }

    public Prefix(int start, int end, char letter)
    {
        _start = start;
        _end = end;
        _letter = letter;
    }
}

TreeNode:

public class TreeNode<T>
{
    private TreeNode _parent = null;
    protected List<TreeNode> _children = new List<TreeNode>();
    private T _value;

    public void AddChild(TreeNode child)
    {
        if (!_children.Contains(child))
        {
            _children.Add(child);
            child._parent = this;
        }
    }

    public TreeNode Parent
    {
        get { return _parent; }
        set { _parent = value; }
    }

    public TreeNode(T value)
    {
        this._value = value;
    }

    public TreeNode(T value, TreeNode[] children)
    {
        new TreeNode(value);
        _children = new List<TreeNode>(children);
    }

    public T Value
    {
        get { return _value; }
        set { _value = value; }
    }

    public List<TreeNode> Children
    {
        get { return _children; }
    }
}

Powyższa klasa jest bardzo prostą implementacją węzła drzewa przechowującego zadany typ danych o zbiorze synów trzymanych za pomocą listy generycznej.

Mając tak zdefiniowane klasy pomocnicze, możemy napisać rekurencyjną metodę tworzącą drzewo trie:

public class Trie
{
    private String[] _input;
    private TreeNode<Prefix> _tree;
    private char[] _availableChars;

    private void MakeNode(TreeNode<Prefix> root, int indexOfString)
    {
        int wordIndex = root.Value.Start;
        int lastIndex = wordIndex;
        bool nodeStarted = false;
        char character = '';
        char prevChar = character;
        while (wordIndex <= root.Value.End)
        {
            String s = _input[wordIndex];
            prevChar = character;
            if (indexOfString < s.Length)
            {
                if (s[indexOfString] != character)
                {
                    character = s[indexOfString];
                    if (nodeStarted)
                    {
                        Prefix newPair = new Prefix(lastIndex, wordIndex - 1, prevChar);
                        lastIndex = wordIndex;
                        TreeNode<Prefix> newNode = new TreeNode<Prefix>(newPair);
                        root.AddChild(newNode);
                        nodeStarted = false;
                    }
                }
                else
                {
                    nodeStarted = true;
                    wordIndex++;
                }
            }
            else
            {
                wordIndex++;
            }
        }
        if (nodeStarted)
        {
            root.AddChild(new TreeNode<Prefix>(new Prefix(lastIndex, root.Value.End, character)));
        }
        foreach (TreeNode<Prefix> child in root.Children)
        {
            MakeNode(child, indexOfString + 1);
        }
    }

    public Trie(String[] input)
    {
        _input = input;
        _tree = new TreeNode<Prefix>(new Prefix(0, _input.Length - 1, ''));
        MakeNode(_tree, 0);
    }
}

Kluczowa jest metoda MakeNode, która w sposób rekurencyjny tworzy listę synów dla istniejących wierzchołków drzewa trie, w oparciu o drugi parametr, który określa indeks bieżącej litery w szukanych stringach.

Mając utworzone drzewo trie dla danego zbioru stringów, możemy wywoływać następującą metodę z klasy Trie:

        public String[] GetStringsForPrefix(string prefix)
        {
            TreeNode<Prefix> current = _tree;
            for (int i = 0; i < prefix.Length; i++)
            {
                current = current.Children.Find(new Predicate<TreeNode<Prefix>>(t => t.Value.Letter == prefix[i]));
                if (current == null)
                {
                    return null;
                }
            }
            int length = current.Value.End - current.Value.Start + 1;
            string[] result = new string[length];
            Array.Copy(_input, current.Value.Start, result, 0, length);
            return result;
        }

która zwróci nam interesujący nas podzbiór stringów. W ten sposób mamy rozwiązanie początkowego problemu.

Powyższa implementacja struktury danych trie z całą pewnością może być bardziej optymalna – chciałem jednak zachować czytelność kodu i zaprezentować ogólną ideę tworzenia drzewa trie, a nie skupiać się na optymalizacji.

Wadą drzew trie jest ich stosunkowo duży rozmiar w pamięci (jak duży? od czego zależy? czy można go zmniejszyć?). Można to poprawić stosując tzw. drzewa Patricia, czyli wariant drzew trie, w których zamiast liter do etykietowania krawędzi (w naszej implementacji: węzłów), stosujemy całe stringi. Takie skompresowane drzewo trie tworzy się przez usuwanie węzłów mających tylko jednego syna i modyfikację drzewa, tak aby skrócić ścieżkę poszukiwań.

Drzewa trie nie są jedynym sposobem na szybkie rozwiązanie opisanego na wstępie problemu, ale warto wiedzieć o ich istnieniu i umieć je zaimplementować. W szczególności dlatego, że w pewnych okolicznościach są wydajniejsze niż drzewa BST.

2009
04.24

W czwartek 23 kwietnia w hotelu Sheraton w Warszawie odbyło się spotkanie Steve’a Ballmera, CEO Microsoftu z członkami polskich społeczności IT. Byłem tam i z przyjemnością opiszę, jak wyglądało to spotkanie.

Steve przybył prawie punktualnie – nie licząc kwadransa akademickiego. Kiedy wchodził na salę miał na sobie czerwono-biały szalik z napisem “Polska”, który dostał od kogoś w Polsce i obiecał powiesić sobie w gabinecie.

Spotkanie zaczęło się od 15-minutowego przemówienia. Właściwie ta prelekcja bardziej przypominała show – Steve poruszał się na scenie oświetlony reflektorami – nie miał żadnych notatek ani pulpitu, nie prezentował żadnych slajdów.

W swoim wystąpieniu Steve poruszył kwestię kryzysu ekonomicznego i jego wpływu na techonologię. Zdaniem prezesa Microsoftu, właśnie teraz – podczas kryzysu – jest czas na inwestowanie w nowe rozwiązania i nowe technologie, które zaprocentują, gdy sytuacja gospodarcza poprawi się.

Ballmer powiedział także, że Microsoft  jest zdecydowany wydać w najbliższym czasie ok. 9 miliardów dolarów na badania i rozwój. Oznacza to 45000 etatów.

W jakich obszarach technologia będzie się rozwijać najbardziej prężnie? Wg CEO Microsoftu głównie w zakresie poprawiania interakcji między użytkownikiem a komputerem: zaawansowane rozpoznawanie mowy czy gestów. Steve wyraził także niezadowolenie ze sposobu w jaki, w dzisiejszych czasach działają wyszukiwarki (tu pozwolił sobie na drobne żarty z konkurencyjnego Google). Przytaczał statystyki, z których wynika, że około połowa poszukiwań konkretnych treści kończy się fiaskiem oraz że przeciętne zapytanie składa się z 2,2 słowa. Powiedział także:

Im więcej informacji dajesz wyszukiwarce, tym mniej zadowalających wyników otrzymujesz. Silnik nie radzi sobie z dużymi liczbami słów w zapytaniu. Gdy rozmawiasz z prawdziwym człowiekiem, jest zupełnie odwrotnie – im więcej informacji dostarczysz, tym lepszą odpowiedź dostaniesz.

Po tym krótkim wystąpieniu nadszedł czas na sesję pytań i odpowiedzi. Pytania dotyczyły najróżniejszych tematów, ale postaram się przytoczyć te najciekawsze:

Gdybyś miał opisać firmę Microsoft za pomocą jednego słowa, jakiego słowa byś użył?

Steve chwilę zastanawiał się, pytając z niedowierzaniem “one word? one word???”, kiedy nagle ktoś z sali krzyknął “awesome!”. Steve podchwycił i powiedział, że gdyby miał do dyspozycji dwa słowa powiedziałby:

Awesome, baby!

Poważna odpowiedź na pytanie brzmiała natomiast:

Popular innovation

Steve wyjaśnił, że Microsoft jest firmą, która dba o innowacje i wdraża nowe rozwiązania, ale jej celem jest poruszenie masowego odbiorcy – wpłynięcie na jak największą liczbę ludzi. Jako kontrprzykład podał firmę Apple – która wg niego jest innowacyjna, ale nie trafia do mas.

Inne pytanie, dotyczyło patentów. Steve wyraził pogląd, że patenty – jeśli reprezentują prawdziwą wartość – mają sens i popiera je. Skrytykował natomiast rozwiązania prawne zarówno w Stanach jak i w Unii, ponieważ według niego pozwalają na zgłaszanie bezsensownych patentów, za którymi nie kryje się żadna wartość.

Zapytany czy statystyki pokazujące, że większość specjalistów IT nie zmigruje do Windows 7 w pierwszym roku po jego wydaniu spowodują opóźnienie wydania nowego systemu, Steve powiedział, że jest to wręcz powód, dla którego Microsoft powinien przyspieszyć datę wydania siódemki. Wyraził także przekonanie, że ci sami specjaliści, którzy podejmą decyzję o pozostaniu przy XP czy Viście w firmach, w swoich domach na pewno bardzo szybko zainstalują nowy Windows.

Spotkanie szybko dobiegło końca, ale pozostawiło bardzo przyjemne wrażenie. Chyba głównie z powodu bezpośredniego, bezpretensjonalnego i bardzo energicznego stylu bycia Steve’a. Podczas całego spotkania utrzymywał żywy kontakt z publicznością, często żartował, swobodnie poruszał się po całej scenie, gestykulował. Bardzo dobrze się go słuchało.

Jedyną wadą spotkania było to, że trwało tylko jakieś 40 minut. No i Steve tym razem nie wykonał popisowego monkey dance… ;)