Author Archives: Paweł Trojanowski

Podstawowe zabezpieczenia ASP.NET MVC

Potraktujcie ten wpis jako wstęp do bardziej zaawansowanych mechanik zabezpieczania aplikacji ASP.NET MVC, które zostaną z pewnością zwrócone do poprawy przez bezpieczników podczas pentestów (testów penetracyjnych).

Usunięcie informacji na temat frameworku i serwera z headera odpowiedzi
Dlaczego te informacje domyślnie znajdują się w odpowiedziach z serwera? Nie wiem… Dzięki tej informacji atakujący bot może poznać wersję frameworka i dostosować atak na konkretne znane błędy dla tej wersji. Co jest ultra niebezpieczne, ponieważ nie zawsze system jest aktualizowany na bieżąco.

W Global.asax dodajemy:
protected void Application_PreSendRequestHeaders(object sender, EventArgs e) { HttpContext.Current.Response.Headers.Remove("X-AspNet-Version"); HttpContext.Current.Response.Headers.Remove("X-AspNetMvc-Version"); HttpContext.Current.Response.Headers.Remove("Server"); }
W Web.config dodajemy:
<system.webServer>
	<httpProtocol>
		<customHeaders>
			<clear />
			<remove name="X-Powered-By" />
		</customHeaders>
	</httpProtocol>
</system.webServer>

CDN.

Optymalizacja kodu .NET (a konkretnie ASP.NET MVC) z mojej perspektywy

Pisanie optymalnego i przejrzystego kodu jest bardzo trudną sztuką, której można się uczyć bez końca a i tak znajdzie się ktoś kto powie że można lepiej (a kto Panu tak s***lił). Tak więc nie będę się chwalił jakim to świetnym programistą jestem(bo nadal pewnie jestem marnym)  tylko podam Wam kilka przykładów zmian, które w przypadku projektów w których uczestniczę przyniosły dobry efekt. Pewnie można by było zamiast jednego obszernego wpisu zrobić całą serię omawiającą wiele technik, ale nie wiem czy będę miał tyle motywacji więc niczego nie obiecuję tylko zapraszam do lektury.

Nie zwracajcie uwagę na kolejność tych zagadnień, bo nie są one ułożone wg. ważności, tylko zaczynam od tych o których najprościej mi się pisze, a później będzie już kosmos 🙂

Array vs List?
Mam wrażenie, że każdy kogo pytam ma na to swoje zdanie, tak więc dołożę też swoje 🙂 Wszystko zależy w jakich sytuacjach będziemy je stosować, jeżeli zamierzasz wyciągnąć z bazy dane i tylko wyświetlić je na stronie, nie wykonując żadnych dodatkowych operacji, to myślę że najlepszym rozwiązaniem będzie użycie tablicy zamiast listy. Jednak jeżeli zamierzasz wykonywać operacje typu dodawanie, usuwanie elementów to skorzystaj z listy. Klasa listy zapewnia metody i właściwości, jak inne klasy kolekcji, takie jak dodawanie, wstawianie, usuwanie, wyszukiwanie, itp. Jest to zamiennik dla tablic, połączonych list, kolejek, itp. Jest lepsza od tablicy ponieważ potrafi dużo szybciej obsługiwać operacje zmiany rozmiaru.

Nie ukrywam że przez swoje lenistwo i brak wiedzy przez wiele lat używałem bezmyślnie list do przechowywania wszystkiego czego się da i nie zwracałem uwagi na to że w jakimś momencie może to być kompletnie nieoptymalne, ale teraz staram się myśleć do czego będę potrzebował obiekty wyciągane np. z bazy danych. Jeżeli nie zamierzam wykonywać skomplikowanych operacji to od razu wybieram Array i choć efekty są niewielkie (pewnie rzędu 1% przy większych kolekcjach) uważam że nie jest wielkim utrudnieniem żeby z tego korzystać.

Nie używaj ToLower jeżeli masz bazę danych/tabelę z CI (Case Insensitive)
CS (Case-Sensitive) i CI (Case-Insensitive) odpowiedzialne odpowiednio za rozróżnianie i nierozróżnianie dużych i małych liter przy operacjach na tekstach (sortowanie, porównywanie, itp.) w bazie danych. Tak więc jeżeli nie chcesz spowolnić działania swojej aplikacji, to zamiast używać w Linq to SQL przy porównywaniu ToLower() dla obu elementów porównania, olej to i skorzystaj z właściwości CI. Case Insensitive możesz ustawić w bazie danych/tabeli lub nawet pojedynczym polu tabeli. Serio to daje na prawdę ogromny przyrost wydajności a koszt programistyczny jest tak niewielki, że aż szkoda gadać.

Asynchroniczne Linq
Sama asynchroniczność to temat rzeka i nie jestem zdecydowanie osobą która powinna się wypowiadać na temat poprawnego użycia takiej czy innej metody do rozwiązania konkretnego problemu, ale mogę opowiedzieć o konkretnym rozwiązaniu które w przypadku ASP.NET MVC rzeczywiście powoduje zwiększenie wydajności.

Z linq korzystam już od wielu lat i uważam że jest to najlepsze co w programowaniu mnie spotkało, ponieważ z jego użyciem w tak prosty i logiczny sposób można wykonywać operacje na kolekcjach a jeszcze do tego wsparcie EntityFramework. Po prostu bajka ! Nie no ok, przesadzam są zapewne lepsze i bardziej wydajne rozwiązania, ale po prostu ja lubię Linq i ostatnio zacząłem korzystać z fajnej właściwości która była w Linq od dawna ale ja nie potrafiłem z niej korzystać. A mianowicie Linq ma metody asynchroniczne.

No ale co z tego wynika? A no to, że można cięższe operacje na kolekcjach przenieść na początek wykonywania kodu i dopiero w momencie gdy dane stają się potrzebne za pomocą komendy await poczekać na wyniki (a już część innego kodu się wykonała, czyli jesteśmy do przodu). Jednak ponieważ w większości korzystam z Linq do EntityFramework to akurat ta właściwość nie jest idealna, bo okazuje się że w tym wypadku niestety nie da się zrobić dwóch wywołań do bazy na jednym kontekście w jednej chwili. Tak więc aby móc odpalić asynchronicznie wyciągnięcie dwóch list z bazy trzeba tworzyć dwa konteksty, a to już nie jest ładne rozwiązanie i nie polecam. Ale jeżeli mamy do wyciągnięcie jedną większą listę to jak najbardziej można zrobić to asynchonicznie i jakiś zysk z tego będzie.

Asynchroniczne Linq z możliwością jego anulowania
Jednak rozszerzenie asynchronicznego Linq o możliwość jego anulowania w trakcie działania dało realny przypływ wydajności w aplikacji którą tworzę. Tutaj dobry jest przykład. Jeżeli klient wchodzi na stronę i przeklikuje się przez różne strony jeszcze zanim załadują się do końca lub zamyka stronę po kliknięciu linka, to ten request który poszedł do serwera jest przetwarzany i operacje na bazie czy też skomplikowane query nadal się dzieją co nie jest zbyt optymalne. A poniższe rozwiązanie ma na celu zatrzymanie wykonania query w przypadku otrzymania informacji o rozłączeniu się klienta lub anulowania wykonywania operacji przez przeglądarkę:

public async Task GetData(CancellationToken cancellationToken)
{
    CancellationToken disconnectedToken = Response.ClientDisconnectedToken;
var source = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, disconnectedToken);
var dictionary = await db.Users.ToDictionaryAsync(x => x.UserId, ..., source.Token);
}

Kod jest mega prosty a daje na prawdę dużo, ale polecam to sprawdzić na własną rękę.

Mówią że samo dodanie do metody w kontrolerze ASP.NET właściwości async podobno poprawia działanie, ale co do tego bym nie był takim optymistą, moim zdaniem właśnie użycie powyższego rozwiązania daje efekt przyspieszenia działania aplikacji, ale to musicie sprawdzić sami.

Zamiast pobierania całego obiektu z bazy danych użyj Select z Linq to SQL i wypełnij obiekt wybranymi danymi
Linq ze względu na swoją specyfikę bardzo ułatwia pobieranie obiektów z bazy danych jednocześnie z ich relacjami. Jednak jest to mega nieefektywne, ponieważ często dane, które pochodzą z relacji są nam zwyczajnie niepotrzebne i niepotrzebnie wydłużają czas pobrania danych. Jest na to wiele rozwiązań ale ja polecam jedno (wg. mnie najwygodniejsze), czyli stworzenie sobie klasy(ewentualnie użycie dictionary), która będzie zawierała tylko te pola które są nam aktualnie potrzebne do wykonania zadania. Następnie za pomocą funkcji Select() wybranie które dane trafią do jakiego pola. Poniższy kod to świetnie ilustruje:

var model = await db.CustomerSet
    .OrderBy(x => x.Code)
    .Select(x => new CustomerDTOViewModel()
    {
        Id = x.Id,
        Code = x.Code,
        Name = x.Name,
        NipOrPesel = x.NIP,
        Address = x.Address,
        City = x.City,
        PostCode = x.PostCode,
        AnyWork = x.Work.Any(),
        Active = x.Active
    }).ToListAsync(source.Token);

Dzięki takiem rozwiązaniu zaoszczędzimy czas na pobranie wszystkich obiektów z tabeli Work, które są w relacji do klienta, tylko po to żeby sprawdzić czy nie ma żadnej pracy. Jest to dużo bardziej efektywne, a w przypadku ASP.NET MVC taki model dużo łatwiej wyświetlić na stronie, a kod jest dużo czystszy zakładając że nie będziemy jeszcze z tymi danymi kombinować na widoku czego nie jestem zwolennikiem, w końcu nie bez powodu mamy MVC żeby oddzielać widok od logiki i danych.

Tutaj muszę jednak dodać że jeżeli jednak uprzemy się żeby nie tworzyć modeli pośrednich, to można użyć poniższego kodu, żeby ograniczyć liczbę ładowanych relacji podczas pobierania z SQL, ale i tak ilość nadmiarowych danych będzie dużo większa niż w przypadku modelu pośredniego.

db.Configuration.LazyLoadingEnabled = false;
db.Configuration.ProxyCreationEnabled = false;

Niezależne od siebie elementy doładowuj AJAXem
Jeżeli masz na jednej stronie kilka elementów, które są bardzo obciążające ładowanie strony a między nimi nie dochodzi do zależności, które trzeba by było w niebanalny sposób rozwiązywać, to najlepszym pomysłem byłoby rozbić pobieranie tych elementów na osobne requesty, które doładują się w różnym czasie ale dzięki temu klient zobaczy już pierwsze działające elementy strony. Oczywiście cokolwiek się robi, trzeba to robić z głową, więc tutaj też nie zachęcam do serwowania stron które będą się doładowywały klientowi przesuwając elementy strony i doprowadzając do szewskiej pasji. Chodzi mi o to, że jeżeli na stronie jest główny element po który klient wchodzi na nią a dodatkowo musi się pojawić gdzieś kalendarz z wydarzeniami, to po co czekać aż cały kalendarz się załaduje razem ze stroną, jak można go doładować w trakcie jak klient przegląda główną treść strony.

CDN.

Czemu konkatenacja stringów jest wolniejsza niż użycie stringbuildera?

O tym że StringBuilder append jest dużo szybszy niż zwykła konkatenacja w języku C# wiedziałem, jednak nie miałem pojęcia jaka jest skala tych różnic i co za tym stoi. Nie zawsze mam czas na to żeby sprawdzać dlaczego coś działa tak a nie inaczej, ale tym razem miałem więc przygotowałem proste omówienie tego zjawiska 😉

String w języku C# (JAVA również) są niezmienne. Oznacza to, że metody działające na łańcuchach nie mogą nigdy zmienić wartości ciągu. Łączenie ciągów za pomocą += działa poprzez przydzielanie pamięci dla zupełnie nowego ciągu, który jest połączeniem dwóch poprzednich. Każda nowa konkatenacja wymaga zbudowania całkowicie nowego obiektu String a co za tym idzie zaalokować część pamięci w której będzie się nowy obiekt znajdował. To zajmuję czas i zwiększa użycie pamięci.

Z drugiej strony StringBuilder wstępnie alokuje bufor, a dodając do niego łańcuchy, nie trzeba go ponownie przypisywać (zakładając, że początkowy bufor jest wystarczająco duży). Co zwiększa wydajność i znacznie mniej obciąża pamięć.

Czy to oznacza że StringBuilder jest zawsze najlepszym rozwiązaniem? No właśnie nie zawsze, ponieważ w przypadku niedużych ciągów znaków ilość pamięci alokowanej i narzut wydajnościowy przewyższa proste użycie Stringa, a kod staje się nieco mniej czytelny niż proste wywołanie return string1 + string2;

Kod porównujący wydajność obu sposobów

public static void ZwyklaKonkatenacja()
{
	var tekst = "";
	Stopwatch test1 = Stopwatch.StartNew();
	test1.Start();
	for (int i = 0; i &lt; 100000; i++)
	{
		tekst += "Y";
	}
	test1.Stop();
	Console.WriteLine("Konkatenacja " + test1.ElapsedMilliseconds + " ms");
}

public static void KonkatenacjaStringBuilder()
{
	var sb = new System.Text.StringBuilder();
	Stopwatch test2 = Stopwatch.StartNew();
	test2.Start();
	for (int i = 0; i &lt; 100000; i++)
	{
		sb.Append("Y");
	}
	test2.Stop();
	Console.WriteLine("StringBuilder " + test2.ElapsedMilliseconds + " ms");
}

Wyniki szybkości działania funkcji

Wpis został zainspirowany przez kolegę z pracy, który ostatnio optymalizuje co tylko się da. Zaskoczył mnie, ponieważ wiedziałem że różnica jest, ale nie spodziewałem się że jest aż tak ogromna. Tak więc wielkie podziękowania.

jQuery – Zaznaczanie tekstu na stronie (podkreślanie)

Dostałem tydzień temu informację że w systemie jaki tworzę nagle zaczął się źle zaznaczać filtrowany tekst w tabelach. A mianowicie zamiast zaznaczać się tylko tekst, który jest wyszukiwany zaczął się zaznaczać tekst niemalże do końca linii. Najdziwniejsze w tej historii jest to że w ciągu tygodnia nie robiłem żadnej aktualizacji systemu, która mogłaby taki błąd wprowadzić. Może coś w przeglądarkach wprowadzono o czym nie wiem?? Może. Ale znalazłem kod, który realizuje to zadanie idealnie:

/*
 * jQuery Highlight plugin
 *
 * Based on highlight v3 by Johann Burkard
 * http://johannburkard.de/blog/programming/javascript/highlight-javascript-text-higlighting-jquery-plugin.html
 *
 * Code a little bit refactored and cleaned (in my humble opinion).
 * Most important changes:
 *  - has an option to highlight only entire words (wordsOnly - false by default),
 *  - has an option to be case sensitive (caseSensitive - false by default)
 *  - highlight element tag and class names can be specified in options
 *
 * Usage:
 *   // wrap every occurrance of text 'lorem' in content
 *   // with <span class='highlight'> (default options)
 *   $('#content').highlight('lorem');
 *
 *   // search for and highlight more terms at once
 *   // so you can save some time on traversing DOM
 *   $('#content').highlight(['lorem', 'ipsum']);
 *   $('#content').highlight('lorem ipsum');
 *
 *   // search only for entire word 'lorem'
 *   $('#content').highlight('lorem', { wordsOnly: true });
 *
 *   // don't ignore case during search of term 'lorem'
 *   $('#content').highlight('lorem', { caseSensitive: true });
 *
 *   // wrap every occurrance of term 'ipsum' in content
 *   // with <em class='important'>
 *   $('#content').highlight('ipsum', { element: 'em', className: 'important' });
 *
 *   // remove default highlight
 *   $('#content').unhighlight();
 *
 *   // remove custom highlight
 *   $('#content').unhighlight({ element: 'em', className: 'important' });
 *
 *
 * Copyright (c) 2009 Bartek Szopka
 *
 * Licensed under MIT license.
 *
 */

jQuery.extend({
    highlight: function (node, re, nodeName, className) {
        if (node.nodeType === 3) {
            var match = node.data.match(re);
            if (match) {
                var highlight = document.createElement(nodeName || 'span');
                highlight.className = className || 'highlight';
                var wordNode = node.splitText(match.index);
                wordNode.splitText(match[0].length);
                var wordClone = wordNode.cloneNode(true);
                highlight.appendChild(wordClone);
                wordNode.parentNode.replaceChild(highlight, wordNode);
                return 1; //skip added node in parent
            }
        } else if ((node.nodeType === 1 && node.childNodes) && // only element nodes that have children
                !/(script|style)/i.test(node.tagName) && // ignore script and style nodes
                !(node.tagName === nodeName.toUpperCase() && node.className === className)) { // skip if already highlighted
            for (var i = 0; i < node.childNodes.length; i++) {
                i += jQuery.highlight(node.childNodes[i], re, nodeName, className);
            }
        }
        return 0;
    }
});

jQuery.fn.unhighlight = function (options) {
    var settings = { className: 'highlight', element: 'span' };
    jQuery.extend(settings, options);

    return this.find(settings.element + "." + settings.className).each(function () {
        var parent = this.parentNode;
        parent.replaceChild(this.firstChild, this);
        parent.normalize();
    }).end();
};

jQuery.fn.highlight = function (words, options) {
    var settings = { className: 'highlight', element: 'span', caseSensitive: false, wordsOnly: false };
    jQuery.extend(settings, options);

    if (words.constructor === String) {
        words = [words];
    }
    words = jQuery.grep(words, function(word, i){
      return word != '';
    });
    words = jQuery.map(words, function(word, i) {
      return word.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
    });
    if (words.length == 0) { return this; };

    var flag = settings.caseSensitive ? "" : "i";
    var pattern = "(" + words.join("|") + ")";
    if (settings.wordsOnly) {
        pattern = "\\b" + pattern + "\\b";
    }
    var re = new RegExp(pattern, flag);

    return this.each(function () {
        jQuery.highlight(this, re, settings.element, settings.className);
    });
};

Łatwa i darmowa synchronizacja danych między komputerami

Lubię posiadać kopię zapasowe swoich danych w wielu miejscach, żeby w przypadku awarii dysku czy też jakiś zdarzeń losowych nie mieć problemu z odzyskiwaniem danych. Dlatego dotychczas używałem chmur plikowych takich jak Dropbox czy OneDrive. Jednak powyżej jakieś ilości składowanych danych w chmurze, trzeba zacząć za takie składowanie danych płacić. Dodatkowy problem, który uważam że jest dość ważny, to to że nie mamy pewności gdzie znajdują się dane i czy ktoś nieuprawniony nie ma do nich dostępu.

Dlatego zacząłem szukać jakiegoś rozwiązania, które miałoby na celu zminimalizować ryzyko utraty danych, działałoby i synchronizowałoby moje dane tylko na moich komputerach i byłoby względnie tanie a najlepiej darmowe.

Takim rozwiązaniem jest Syncthing, który jest multiplatformową aplikacją do wymiany danych między urządzeniami. Nie wymaga kupna serwera, czy zewnętrznego stałego adresu IP i jest całkowicie darmowe.

W moim przypadku akurat to oprogramowanie jest uruchomione na serwerze 24h/7, ale nie jest to konieczne. Wystarczy włączyć komputery, które mają się synchronizować.

Muszę przyznać, że byłem bardzo zaskoczony ilością funkcji zawartych w tym programie. Okazało się, że jest możliwość tak jak w Dropboxie tworzyć historię zmian plików, udostępniać udział tylko do odczytu (co jest bardzo ważne w przypadku gdy nie chcemy żeby ktoś miał możliwość nadpisania pliku), czy też synchronizować dane z komórki.

W celu udostępnienia udziału kolejnemu komputerowi wystarczy wygenerować specjalny kod, który po przesłaniu w formie tekstowej lub QR może zostać synchronizowany na innym komputerze, ale dopiero po akceptacji na komputerze udostępniającym dane.

Oczywiście nie warto 100 procentowo ufać żadnemu oprogramowaniu, dlatego też zalecam jednak wrażliwe dane synchronizować po uprzednim spakowaniu archiwizatorem z odpowiednio trudnym hasłem.

4Mobility w Poznaniu pierwszy tydzień testów

Temat car-sharingu jest na tyle nowoczesny, że gdy pojawiła się sposobność skorzystałem z okazji i opisuję tu moje spostrzeżenia, choć to temat nie związany bezpośrednio z informatyką, ale czy na pewno? Zwłaszcza, że do obsługi tej usługi używane jest dość sporo technologii, która jak wynika z moich testów działa (ale czy idealnie?).

Co to w ogóle jest car-sharing?

Wg. Wikipedii jest to „system wspólnego użytkowania samochodów osobowych. Samochody udostępniane są za opłatą użytkownikom przez operatorów floty pojazdów, którymi są różne przedsiębiorstwa, agencje publiczne, spółdzielnie, stowarzyszenia lub grupy osób fizycznych.

Stosowanie tego systemu zwiększa intensywność wykorzystania pojazdów w ciągu doby, co prowadzi do zahamowania wzrostu liczby samochodów rejestrowanych prywatnie.”

Czyli jeżeli potrzebuję się przemieścić w mieście, to odpalam aplikację wybranego car-sharingu i szukam najbliższe auto, rezerwuję i gdy podejdę do niego mogę otworzyć odpalić i pojechać w docelowe miejsce w granicach działania usługi (w przypadku Poznania jest to granica miasta z wyjątkami).

Dlaczego zająłem się akurat 4Mobility skoro już kilka innych firm działa w Poznaniu?

Mówiąc krótko, zachęcili mnie pojazdami i ofertą. Zwykle w ramach car-sharingu mamy do czynienia z małymi ekonomicznymi pojazdami, które nie są zbyt dynamiczne, ale pozwalają zmieścić się na już i tak ograniczonych miejscach parkingowych w centrum polskich miast. Jednak tutaj jest inaczej, ponieważ dostajemy do użytkowania auta z segmentu premium, czyli np. Audi A3 sportback, A3 limousine, czy Q3. Nie są to małe auta, ale dają zdecydowanie więcej frajdy z jazdy niż auta miejskie. Dodatkowo oferta na start jest bardzo dobra, jednak nie będę jej tutaj przytaczał, ponieważ podejrzewam że niedługo się zmieni a nie chciałbym robić reklamy/antyreklamy usługi.

Jakie są moje odczucia po tygodniowych testach?

Audi A3

Muszę przyznać że jest wiele plusów tej usługi jak i kilka minusów. Zaczynając od plusów, to właśnie świetne auta, bardzo korzystna cena, dobre rozłożenie aut na mapie poznania (nie zdarzyło mi się żebym był dalej niż 1 km od auta) oraz darmowe parkingi miejskie.

Są również minusy, takie jak źle działająca aplikacja mobilna na moim telefonie (Samsung Galaxy S8), niestety aplikacja działa niestabilnie, crashuje się, przyciśnięcie jakiegokolwiek przycisku jest bardzo ciężkie i wymaga wielokrotnego wciskania. Zdarzył mi się też mały incydent, że nie mogłem zamknąć auta z uwagi na brak karty paliwowej lub dowodu rejestracyjnego, których nie ruszałem a które znajdowały się w slocie gdzie było ich miejsce. Jedak po chwili udało mi się rozwiązać problem wyciągając i wkładając te dokumenty z powrotem do slotu.

Czy to są poważne problemy, oczywiście że nie i zakładam że w ciągu najbliższych tygodni zostaną rozwiązane a nam pozostanie jedynie cieszyć się dynamiczną jazdą świetnymi, nowymi autami.

Idea wszystko jako usługa

Od jakiegoś czasu nawet tutaj na blogu promuję podejście „Everything as a service (EaaS)”, która jest mi szczególnie bliska, ponieważ sam tworzę rozwiązania, które aspirują do takiego miana. Jest to bardzo wygodne rozwiązanie dla ludzi, którzy na co dzień nie potrzebują danego dobra, a gdy nagle przez chwilę jest im to dobro potrzebne, to nie ma sensu żeby je kupowali. Lepiej w takim wypadku w jakiś sposób to dobro wypożyczyć, co jest świetnie realizowane w chmurach, właśnie car-sharingu czy też innych usługach opartych o ten model. Rozumiem, że moje podejście jest związane z tym że mieszkam w dużym mieście i mam dostęp do tego typu dóbr, ale skoro już mieszkam to czemu nie mam sobie ułatwiać życia i zamiast posiadać własny rower, korzystać z roweru miejskiego. Zamiast kupować auto miejskie przemieszczać się car-sharingami, itp.

Czy to ma sens?

Myślę, że firmy, które aktualnie inwestują w ten typ usług nie zarabiają bo są prekursorami i ludzie jeszcze nie rozumieją i trochę się boją korzystać z tych usług. Jednak zakładam że w ciągu kilku lat ludzie nauczą się korzystać z tego i doceniać wartość a wtedy właśnie te firmy, które weszły stosunkowo wcześnie już będą miały gotową infrastrukturę i niemalże monopol na świadczenie danej usługi. Tak więc wróżę duży sukces tego typu usług, tylko mam nadzieję że pozostaną tak tanie jak obecnie, bo ja już zacząłem korzystać.

Wysłałem maila z kilkoma pytaniami odnośnie tej usługi, więc jak tylko dostanę odpowiedź, rozszerzę artykuł o konkretne informację, być może również techniczne (jeżeli firma uchyli rąbka tajemnicy).

Gdzie przenieść środowisko programistyczne (chmura, dedyk, vps)?

W dzisiejszych czasach wszystko związane z IT zaczyna przenosić się do sieci, chodzi mi o streamingi muzyki, filmów, pliki archiwizujemy na dropboxach czy innych drive’ach. Dlatego zadałem sobie pytanie, czy nie byłoby dobrym pomysłem przenieść swoje środowisko programistyczne do sieci?

W jednym z poprzednich wpisów opisywałem że kupiłem sobie komputer z dość dobrymi podzespołami do moich rozwiązań programistycznych i z uwagi na wydajne i stabilne łącze pracuje na nim zamiast lokalnie na komputerze. Jednak ostatnio zacząłem coraz bardziej odczuwać minusy tego rozwiązania, ponieważ dostawca internetu zaczął szwankować a ja kilka razy w miesiącu przez kilka godzin nie miałem dostępu do serwera. A to nie może się zdarzać, bo jednak w przypadku spotkania z klientem tłumaczenie że pewnie internet w domu się zawiesił może postawić mnie w złym świetle, a dodatkowo każda godzina przestoju w pracy kosztuje coraz więcej.

Zacząłem więc szukać alternatywnych rozwiązań. Pierwszym rozwiązaniem o którym pomyślałem była chmura. Miała wiele zalet, np. to że mogłem szybko i tanio utworzyć instancję a następnie płacić tylko za godziny pracy serwera. To rozwiązanie się bardzo dobrze sprawdza pod względem technicznym, ale finansowo jednak nie do końca. Okazało się że w ciągu miesiąca pracy w takim środowisku kilka razy zdarzyło mi się nie wyłączyć serwera po pracy a co za tym idzie przez następne kilkanaście godzin naliczane były mi opłaty za działający serwer. W wyniku takich niedopatrzeń niestety w ciągu miesiąca straciłem tyle pieniędzy za ile miałbym serwer dedykowany dostępny 24h na dobę. Dodatkowym minusem jest niewielka ilość powierzchni dyskowej na dysku systemowym, przez co byłem zmuszony dopłacić za dodatkowy dysk SSD co już znacząco przekracza próg opłacalności tego rozwiązania. Najwidoczniej do tego chmura się nie najlepiej nadaje.

Zwróciłem również uwagę na VPSy, jednak z tego rozwiązania zrezygnowałem na starcie z uwagi na ograniczenia ilości rdzeni na maszynę, wolne dyski, bo jednak współdzielone i stosunkowo wysoki koszt, ponieważ serwer w takiej konfiguracji jaką wymagam nie różnił się praktycznie ceną z dedykiem w podobnej konfiguracji w jednej z polskiej serwerowni.

W końcu zwróciłem się w kierunku serwera dedykowanego i tutaj oferty są na prawdę korzystne, ponieważ za dobry serwer z 4 rdzeniami ponad 3Ghz, 32GB Ram i dwoma dyskami 120 GB i Windows Server 2012 z RDS za 300 zł netto. Pewnie można taniej, ale akurat taka konfiguracja mi odpowiada w 100%. Plusy są takie same jak powyżej, ale nie mamy minusa w postaci zmiennej kwoty w zależności od nieuwagi, a dodatkowo zawsze można postawić na takim serwerze coś więcej niż tylko środowisko programistyczne.

Na ten moment ciężko mi określić czy takie rozwiązanie jest idealne, ale na pewno będę je dalej sprawdzał i napiszę za jakiś czas tak jak teraz kontynuuję wpis o pracy na serwerze zdalnym.

Jak zabezpieczyć środowisko programistyczne przed awarią

Prawdopodobnie każdemu z Was kiedyś przytrafiła się awaria dysku twardego, czy też kupno nowego komputera i odtworzenie środowiska programistycznego na innym komputerze kosztowało Was dużo pracy i czasu, a niektóre dobrze skonfigurowane programy już nigdy nie były tak wygodne jak wcześniej.

Był to dla mnie poważny problem, którego nie potrafiłem przez dłuższy czas rozwiązać i choć systemy backupowania potrafią już robić kopię całych partycji, było to dla mnie zbyt uciążliwe i wymagało mojej uwagi. Jednak po zakupie porządnego komputera do programowania zacząłem się znów interesować wirtualizacją i to podsunęło mi świetny pomysł, że po co mam pracować bezpośrednio na maszynie jak mogę pracować na maszynie wirtualnej.

I teraz pewnie odezwą się głosy, że tracę część mocy obliczeniowej na środowisko hypervisora, że łącząc się przez pulpit zdalny ze swoją maszyną wirtualną mam niewielkie lagi i utraty płynności wyświetlanego obrazu i nawet z tymi głosami mogę się zgodzić, ale takie rozwiązanie ma także wiele zalet.

A zaletą jest to, że gdy chcę zrobić backup maszyny wirtualnej, to mogę skorzystać z metody snapshotów (do której nie jestem przekonany) lub po prostu zatrzymać maszynę wirtualną na kilka minut i przekopiować pliki na dysk sieciowy. Czyli zapewniam sobie w ten sposób kopię całego środowiska ze wszystkimi programami, plikami i przeglądarkami, które potrzebuję i w razie potrzeby mogę szybko odtworzyć na innym komputerze maszynę wirtualną i dalej pracować. Dzięki czemu nie tracę czasu w przypadku poważnych problemów.

Kolejną zaletą jest oddzielenie środowiska developerskiego od środowiska prywatnego, bo jednak co by nie powiedzieć jestem trochę bałaganiarzem w trakcie pracy i mój pulpit szybko zapełnia się różnego rodzaju śmieciami, których nie chciałbym mieć na prywatnym komputerze. A w ten sposób mam jeden komputer, który jest jednocześnie prywatnym i firmowym.

Niektórzy korzystają z mechanizmu snapshotów czego ja akurat nie potrzebuję, ale widzę pewne plusy tego rozwiązania, takie jak możliwość cofnięcia się do punktu z przed zaśmiecenia systemu zbędnymi danymi. Jednak ja wyszedłem z założenia, że i tak jak będę chciał się cofnąć to po prostu załaduję odpowiedni backup, który jest kopią całościową a snapshoty są przyrostowe, czyli w przypadku kopiowania tylko kolejnych plików przyrostowych jest to mniej bezpieczna metoda.

Ewentualnie polecam również stworzenie sobie środowiska developerskiego w chmurze, ponieważ tam też mamy możliwość ustalenia backupów całościowych, kopiowania środowiska i mamy dostęp praktycznie wszędzie, ale jest to rozwiązanie niestety dosyć drogie. Nawet jeżeli zdecydujemy się włączać instancję tylko na czas pracy, to wychodzi około 200 zł miesięcznie, a to pozwala w ciągu roku kupić już jakiś w miarę dobry komputer do programowania.

Podsumowując: By dobrze zabezpieczyć się przed utratą danych, programów i konfiguracji warto pracować na wirtualnej maszynie. Skonfigurowanie i zainstalowanie systemu na środowisku wirtualnym chwilę trwa, jak każda instalacja systemu na nowym komputerze, ale po jakimś czasie bardzo możliwe że się zwróci w postaci łatwego przeniesienia środowiska na inny komputer.

5 błędów które popełniają młodzi programiści

Nikt nie jest idealny i zdaję sobie sprawę że nie można od nikogo tego wymagać, jednak mój wpis nie ma za zadanie wyśmiewać się z błędów młodych programistów tylko zwrócić uwagę na błędy, które popełniają. Jest to lista całkowicie subiektywna i prawdopodobnie nie zawiera 99% błędów popełnianych przez programistów, ale akurat z takimi sytuacjami się spotkałem i wiem że po poprawie tych błędów było już dużo lepiej.

  1. Nie używanie breakpointów i nie sprawdzanie wartości zmiennych – nie wiem czy to z lenistwa, czy niewiedzy zauważyłem że nowo zatrudnieni programiści nie używają breakpointów w trakcie pracy nad problematycznym kodem. Rozumiem że niektórych błędów się można domyślić, ale przejście wykonania kodu krok po kroku ułatwia zrozumienie co się dzieje, co oczywiście polecam.
  2. Nie szukanie rozwiązań problemów w internecie – tutaj prawdopodobnie jest to lenistwo, ponieważ łatwiej zapytać kolegę z pracy jak rozwiązać problem niż zacząć szukać na własną rękę, ale polecam jednak spróbować najpierw szukać w google i jeżeli w krótkim czasie nie znajdziesz problemu, spytać współpracownika. Każde pytanie zadane współpracownikowi wybija go z pracy, więc warto ograniczać to tylko do najważniejszych tematów.
  3. Nie proszenie o pomoc lub proszenie o pomoc zbyt częste – tutaj przewija się temat zbyt częstego zadania pytań z poprzedniego punktu, ale również dodałem element nie pytania w ogóle. Rozumiem, że czasami programista nie chce ujawnić swojej niewiedzy, ale wierzcie mi lub nie, niewiedza i tak wyjdzie prędzej czy później. Więc nie bój się pytać jeżeli nie masz już żadnego sensownego pomysłu na rozwiązanie problemu.
  4. Nie przewidywanie nietypowych warunków działania algorytmu – to jest notoryczny problem nowych programistów, ponieważ nie rozwiązali odpowiedniej ilości błędów i nie potrafią ich przewidzieć. Tutaj mogę doradzić tylko jedno, próbuj zepsuć działanie programu na wszelkie sposoby po napisaniu kodu, a jeżeli ktoś wytknął Ci błąd, to podpatrz jak on doszedł do tego że go popełniłeś.
  5. Nie szukanie rozwiązań problemu w projekcie – ten punkt trochę wynika również z nieznajomości projektu w którym się piszę kod. Ale nie można się tak tłumaczyć, ponieważ nawet jeżeli nie znamy projektu w 100% to możemy przynajmniej spróbować poszukać, czy w kodzie któregoś modułu nie ma jakiegoś kawałka kodu który przyda się podczas tworzenia kodu. Ważne jest żeby inni pracownicy również uważali na swój kod i pisali go możliwie uniwersalnie. Zasada jest taka: jeżeli masz napisać jakąś funkcję, która się przyda w wielu miejscach projektu to przemyśl lub zapytaj współpracownika gdzie taki kod powinien się znaleźć. W różnych firmach są różne podejścia i czasami w zależności od zastosowania kodu powinien on znaleźć się w określonym miejscu i w takim momencie musisz pytać bo sam możesz nie dojść do tego jak zrobić to dobrze.

A dla ludzi współpracujących z młodymi programistami polecam code review. Wiem że to brzmi jak strata czasu, ale tylko w ten sposób jesteście w stanie wcześnie wykryć złe nawyki programisty i nauczyć go tego co w waszej firmie jest pożądane.

Wyszukiwanie binarne [C#]

Wyszukiwanie binarne jest algorytmem opierającym się na metodzie dziel i zwyciężaj, który w czasie logarytmicznym stwierdza, czy szukany element znajduje się w uporządkowanej tablicy i jeśli się znajduje, podaje jego indeks. Algorytm, który napisałem korzysta z bibliotek dodatkowych (linq czy Collections.Generic), które są dostępne w C# a niekoniecznie będą dostępne w innych językach programowania, ale jeżeli chodzi o założenia algorytmu to jest on przepisywalny w łatwy sposób na C++ czy JAVA bo semantyka tych języków jest bardzo podobna.

Ważne: Działa on dla listy o wielkości większej niż 1. Nie wdrażałem tego zabezpieczenia z uwagi że generuję losowo listę o ilości elementów większej niż 1.


using System;
using System.Collections.Generic;
using System.Linq;

namespace Wyszukiwanie_binarne
{
    class Program
    {
        static void Main(string[] args)
        {
            Random rand = new Random();

            // tworzę listę i wypełniam losowymi danymi
            List<int> listaLiczb = new List<int>();
            for (int i = 0; i < 100; i++) listaLiczb.Add(rand.Next(0, 1000));

            // czyszczę listę z powtórzonych wartości i sortuję
            listaLiczb = listaLiczb.Distinct().ToList();
            listaLiczb.Sort();

            Console.WriteLine("Liczby na liście:");
            Console.WriteLine(String.Join(",", listaLiczb));

            Console.WriteLine("Podaj liczbę której pozycję szukasz:");
            var szukanaLiczba = int.Parse(Console.ReadLine());

            Console.WriteLine("Poszukiwana liczba znajduje się na liście pod indexem: ");
            try
            {
                Console.Write(Szukaj(listaLiczb, szukanaLiczba));
            }
            catch (Exception)
            {
                Console.Write("Podana liczba nie znajduje się na liście!");
            }

            Console.ReadKey();
        }

        public static int Szukaj(List<int> przeszukiwanaLista, int szukanaLiczba)
        {
            // ilość elementów w liście
            var iloscElementow = przeszukiwanaLista.Count();

            // jeżeli ilość elementów wynosi jeden to znaczy że liczba nie została znaleziona
            if (iloscElementow == 1) throw new Exception("Podana liczba nie znajduje się na liście!");
            
            // wyliczenie połowy z ilości elementów
            var polowa = iloscElementow / 2;

            // wyciągnięcie z listy wartości środkowego elementu
            var srodkowyElement = przeszukiwanaLista[polowa];

            // jeżeli wartość środkowego elementu jest większa niż szukana liczba, to weź pierwszą połowę listy i przekaż do funkcji Szukaj()
            if (srodkowyElement > szukanaLiczba) return Szukaj(przeszukiwanaLista.Take(polowa).ToList(), szukanaLiczba);

            // jeżeli wartość środkowego elementu jest mniejsza niż szukana liczba, to weź drugą połowę listy i przekaż do funkcji Szukaj()
            else if (srodkowyElement < szukanaLiczba) return Szukaj(przeszukiwanaLista.Skip(polowa).ToList(), szukanaLiczba) + polowa; // tutaj dodajemy index połowy, poniważ znaleziona liczba będzie z drugiej połowy listy

            // Jeżeli jest równa co szukana liczba to zwróć index aktualnej połowy
            return polowa;
        }
    }
}