Categories
C#

Czy ufasz programom?

Wpis [part not set] z 8 w serii C# - podstawy

Zaufanie to złożone zagadnienie. W zależności od naszych doświadczeń nasz poziom zaufania do ludzi jest różny. Jeżeli w przeszłości nieprzyjemnie sparzyliśmy się względem drugiej osoby to trudno nam przyjdzie jej zaufać. Z drugiej strony, w przypadku solidnej współpracy łatwiej nam przyjdzie powierzyć innemu człowiekowi nasze tajemnice czy dobra materialne.

I choć daleki jestem od personifikowania oprogramowania to dostrzegam tutaj kilka podobieństw. Odpowiedz proszę na pytanie: jak często aktualizujesz programy na Twoim komputerze? Czujesz, na ekranie pojawia się nowe okno Dostępna jest aktualizacja, stajesz przed wyborem: Aktualizuj lub Odłóż na później. Którą pigułkę wybierzesz? Jeżeli w przeszłości miałeś problemy po lub w trakcie aktualizacji to nie będziesz skory do ponownego wskoczenia do tej samej rzeki – będziesz odkładał update w nieskończoność. Przyczyną tego zachowania była utrata zaufania – poprzedni proces aktualizacji nie przebiegł tak bajkowo jak obiecywał ekran z nowościami.

A teraz podkręćmy miernik konsekwencji. Twoje biuro projektowe zakupiło nowiutki, lśniący program do MESu. Sprzedawca obiecywał, że teraz połowa dotychczasowej roboty zrobi się sama a raporty będą tak czytelne, że nawet Twoja teściowa zrozumie jak redukowane są momenty zginające nad słupami. Uruchamiasz nowy soft, poznajesz jego interfejs, modelujesz pierwsze konstrukcje. Myślisz – kurczę, faktycznie kawał dobrego programu, pracuje się aż miło – trzyma Cię jeszcze fala początkowego entuzjazmu. Wypluwasz raport, za raportem, koledzy z biurek obok patrzą z niedowierzaniem jak bardzo (znów) jarasz się pracą. Jeden z nich, najbardziej doświadczony podchodzi, patrzy na przebieg wykresu sił wewnętrznych w belce i kiwa z niedowierzaniem głową. Odkrył błąd, gruby błąd w Twojej najnowszej zabawce, który spowodowałby nadmierne zarysowania konstrukcji i uruchomiłby ulubioną grę budowlańców: spier*lo-pong, czyli wzajemne przerzucanie się odpowiedzialnością kto zawinił. W tym wypadku, winnym byłbyś Ty – za bardzo zaufałeś nowemu programowi, którego producent w warunkach użytkowania zastrzegł, że całą odpowiedzialność zrzuca na Ciebie, bo jesteś przecież doświadczonym inżynierem.

Trochę przejaskrawiłem, wiem, ale potrzebowałem tej historii żeby wskazać, że błędy w oprogramowaniu mogą być bardzo kosztowne i zazwyczaj to użytkownik ponosi ich konsekwencje. Błędy, czyli bugi towarzyszą rozwojowi softu od pierwszych dni i pozostają z nim, większe lub mniejsze, przez cały czas. Programowanie to złożony proces, to wykuwanie nowej, wirtualnej materii przy pomocy potęgi ludzkiego umysłu i sprawności palcy. Czasami, gdzieś pomiędzy kolejnymi uderzeniami klawiszy do kodu źródłowego wkradną się błędy .

Bugów nie unikniemy mówiąc pracuj uważniej, skup się, przestań myśleć o porannej walce o założenie czapki przez Twoje dziecko. One były, są i będą – lepiej zaakceptujmy ten fakt i zastanówmy się jak minimalizować ich częstotliwość, jak szybko je wykrywać.

Spójrz na poniższą metodę. Czy potrafisz na podstawie tego screena powiedzieć jaka będzie wartość Fctm dla klasy betonu C40/50?

Ja, jako autor, również z marszu Ci nie powiem, muszę sie zastanowić, przeanalizować flow działania. Program składa się z setek, tysięcy, milionów podobnych metod, z których każda wykonuje inne czynności. Szukanie błędów metodą organoleptyczną, scrollując po kodzie to ostateczność, często konieczna, ale w ostatniej fazie, kiedy już wiemy, że gdzieś tam czai się błąd. Tylko skąd wiemy, że program posiada błędy?

Możemy poczekać na maile/telefony od zdenerwowanych użytkowników, którzy napotkają na bugi. To jazda po bandzie, zwłaszcza w przypadku płatnego oprogramowania, gdzie użytkownik oczekuje jakości za wydane pieniądze. Nie traktujmy ich jako testerów.

Właśnie, testerzy! Może by tak zatrudnić nowych ludzi do testowania albo poprosić kolegę obok żeby między narysowaniem szachtu a zamodelowaniem biegu schodowego sprawdził nasz program. Na pierwsze nie ma pieniędzy, na drugie nie ma czasu. Przynajmniej w klasycznym środowisku budowlanym.

Potrzebujemy innego rozwiązania, czegoś co dawałoby poczucie pewności i niezawodności tworzonego przez nas oprogramowania. Czegoś, co samo testowałoby program, co sygnalizowałoby błędy zanim zauważy je użytkownik. Potrzebujemy programu testującego inny program – potrzebujemy testów jednostkowych.

Testy jednostkowe to specyficzny rodzaj testowania aplikacji. Kontroli poddawane są bardzo małe fragmenty kodu, zazwyczaj pojedyncze metody. Skoro średniej wielkości program składa się z tysięcy metod – testów też muszą być tysiące. Obawiasz się pewnie, że wykonanie takiej liczby testów to długie godziny żmudnej procedury. Uspokoję Cię, że testy jednostkowe mają ukierunkowane działanie, są szybkie przez co ich wykonanie to ułamki sekundy. Dlatego całkiem zgrabnie skaluja się wraz z rozwojem aplikacji.

Testy spełniają jedną podstawową rolę – dają programiście możliwość szybkiego sprawdzenia, że jego nowe zmiany nie zepsuły poprzedniej funkcjonalności programu. Pracując nad dużą aplikacją w wieloosobowym zespole nie sposób znać wszystkich smaczków i niuansów nagromadzonych w wyniku wielu miesięcy/lat pracy. Testy jednostkowe dają Ci spokój ducha i pozwalają sprawdzić przykazanie programisty: po pierwsze nie spier* tego co już działa.

Oczywiście testowanie odizolowanych fragmentów aplikacji z użyciem testów jednostkowych nie gwarantuję, że po zebraniu ich w większe bloki funkcyjne dalej wszystko będzie działało. Istnieją różne strategie testowania, na potrzeby tego wpisu skupię się wyłącznie na testach jednostkowych, bo chciałbym sprawdzić czy uzyskuję poprawne obiekty naszej nowej klasy Concrete.

Pierwszym krokiem jest dodanie nowego projektu do naszej solucji. Tym razem w polu wyszukiwania wpisz NUnit i wybierz wersję dla .NET Framework. NUnit to jedna z możliwych platform testowych, czyli zestaw narzędzi, które umożliwiają sprawne tworzenie i wykonywanie testów.

Standardowa konwencja nazywania projektów z testami to: <NazwaTestowanegoProjektu>.Tests, dlatego ja użyję nazwy ConcreteLibrary.Tests. Po utworzeniu projektu zmienię jeszcze nazwę początkowego pliku .cs w ramach nowego projektu na ConcreteTest – bo to właśnie klasę Concrete będę chciał przetestować.

Czas na dodanie zależności pomiędzy naszym nowym projektem a tym co już było. W tym celu kliknę PPM na references i wskażę w nowym oknie ConcreteLibrary.

Warto jeszcze zadokować nowe okno w Visual Studio, które umożliwi szybki podgląd wyników testowania. Włącz Test Explorer’a, a następnie przeciągnij go w obszar, gdzie dotychczas widziałeś Twoje projekty w formie drzewa (Solution Explorer):

Ok, pora na pierwszy test. Zacznę od negatywnego scenariusza, czyli próby stworzenia obiektu Concrete dla nieobsługiwanej klasy betonu, czyli czegoś co nie zaczyna się od litery C i nie posiada w swojej nazwie dokładnie jednego znaku /. Niech na początku będzie to pusty string. Próba utworzenia obiektu w tej sytuacji powinna zakończyć się rzucenie wyjątku (błędu w trakcie wykonywania). Sprawdźmy czy tak się stanie.

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace ConcreteLibrary.Tests
{
    [TestClass]
    public class ConcreteTest
    {
        [TestMethod]
        public void Empty_concrete_name_is_invalid()
        {
            Assert.ThrowsException<ArgumentNullException>(() => new Concrete(""));
        }
    }
}

Nazewnictwo testów to przedmiot sporów i dyskusji akademików i praktyków. Ja preferuję nazewnictwo ‘naturalne’, czyli zapis prawie jak zdanie, które oddaję sens tego testu. Nazewnictwo testów jest kluczowe kiedy któryś z testów przestaje przechodzić – zwraca błąd. Przy jasnych i klarownych nazwach wystarczy wtedy rzut oka na nazwę testu żeby mieć pojęcie co poszło nie tak.

Ok, mamy zadeklarowany test, warto by sprawdzić czy on przechodzi. W tym celu przejdź do nowo dodanej zakładki Test Explorer i wybierz przycisk Run. Po chwili otrzymasz rezultat Twojego testu – zielono, znaczy jest dobrze.

Patrząc na nasz pierwszy test coś przyszło mi do głowy. Co się stanie jeżeli ktoś spróbuje utworzyć nowy obiekt klasy Concrete nie podając nic, przekazując do konstruktora null? No właśnie, sprawdźmy to pisząc nowy test i wykonajmy go.

Pięknie – program się wysypał! Ha, udało się w porę opatrzyć to zachowanie testem i sprawdzić zachowanie w kontrolowanych warunkach. To oznacza, że nasz konstruktor nie wie co zrobić w tej sytuacji. Zmieńmy to zachowanie modyfikując go w źródłowym projekcie ConcreteLibrary:

        public Concrete(string name)
        {
            if(string.IsNullOrEmpty(name))
                throw new ArgumentNullException(nameof(Name), "Provided concrete class name is not allowed");

            Name = name;

            InitalizeConcreteProperties();
        }

Nowością są linijki 3 i 4 w powyższym listingu. Sprawdzają one czy przekazany tekst w ogóle istnieje i czy przypadkiem nie jest pusty. Jeżeli taka sytuacja zaistnieje to zwrócony zostanie wyjątek.

Zmieniliśmy to co wychwycił test – sprawdźmy czy nasza zmiana zadziała i (co najważniejsze) czy nie zepsuliśmy tego co już działało. Po uruchomieniu wszystkich testów widzę już tylko kolor zielony – jest dobrze 🙂

Dodajmy jeszcze co najmniej dwa testy sprawdzające wartości powstające w trakcie inicjalizacji betonu. Dla Fctm mieliśmy instrukcję warunkową, która w zależności od wartości wytrzymałości na ściskanie wyznaczała kolejny parametr z różnych wzorów – sprawdźmy czy to działa. Tylko najpierw musimy do naszego testowego projektu dodać odwołanie do paczki nugetowej UnitsNet – tej od zamiany jednostek. Zrób to proszę identycznie jak w poprzednim wpisie.

Zestaw czterech testów sprawdzających podstawowe zachowanie konstruktora może wyglądać tak:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;

namespace ConcreteLibrary.Tests
{
    [TestClass]
    public class ConcreteTest
    {
        [TestMethod]
        public void Empty_concrete_name_is_invalid()
        {
            Assert.ThrowsException<ArgumentNullException>(() => new Concrete(""));
        }

        [TestMethod]
        public void Null_concrete_name_is_invalid()
        {
            Assert.ThrowsException<ArgumentNullException>(() => new Concrete(null));
        }

        [TestMethod]
        public void Check_concrete_parameters_when_less_than_50_MPa()
        {
            Concrete concrete = new Concrete("C30/37");

            double delta = 0.01;
            Assert.AreEqual(concrete.Fck.Megapascals, 30, delta);
            Assert.AreEqual(concrete.Fck_cube.Megapascals, 37, delta);
            Assert.AreEqual(concrete.Fcm.Megapascals, 38, delta);
            Assert.AreEqual(concrete.Fctm.Megapascals, 2.90, delta);
            Assert.AreEqual(concrete.ElasticModulus.Gigapascals, 32.837, delta);
        }

        [TestMethod]
        public void Check_concrete_parameters_when_greater_than_50_MPa()
        {
            Concrete concrete = new Concrete("C55/67");

            double delta = 0.01;
            Assert.AreEqual(concrete.Fck.Megapascals, 55, delta);
            Assert.AreEqual(concrete.Fck_cube.Megapascals, 67, delta);
            Assert.AreEqual(concrete.Fcm.Megapascals, 63, delta);
            Assert.AreEqual(concrete.Fctm.Megapascals, 4.21, delta);
            Assert.AreEqual(concrete.ElasticModulus.Gigapascals, 38.214, delta);
        }
    }
}

Czy cztery testy wystarczą? To zależy 😉 Wraz z pisaniem kolejnych metod w ramach klasy beton powinniśmy dodawać coraz to nowe testy, obejmujące nowe funkcjonalności. W ten sposób niejako jednocześnie rozwijamy naszą aplikację i naszego prywatnego testera, który będzie na nasze zawołanie sprawdzał efekty naszej pracy – przyznasz, że to kusząca perspektywa.

Categories
C#

Węzeł betoniarski

Wpis [part not set] z 8 w serii C# - podstawy

(dźwięk wybierania połączenia)
– Halo – słychać znudzony głos operatora
– Dzień dobry, w przyszłym tygodniu będziemy wylewać przyczółek wiaduktu na tej nowej obwodnicy, będziemy potrzebować tak z 80 m3 betonu – mówi rozentuzjazmowany praktykant
– Dobrze, a jakiego betonu potrzebujecie?
– To będzie mieszanka o fck = 40, fcm = 48, fctk,0,05 =2.5, Ecm = 35 …
– Panie, ocipiał Pan? Powiedz Pan jaka ma być klasa betonu.
– Aaa, o to chodzi. C40/50 … ale to Panu wystarczy?
– Mieszam te kamyki z cementem od 32 lat, wiem co to znaczy C40/50. Jestem jak fabryka. Niech Pan poda dokładny adres dostawy. A i połowa płatności z góry …

W poprzednim poście stworzyliśmy klasę Concrete, odpowiadającą inżynierskiemu pojęciu klasa betonu.

    public class Concrete
    {
        public string Name { get; set; }
        public double ElasticModulus { get; set; }

        public Concrete(string name, double elasticModulus)
        {
            Name = name;
            ElasticModulus = elasticModulus;
        }

        public double CalculateEffectiveElasticModulus(double creepCoefficient)
        {
            return ElasticModulus / (1 + creepCoefficient);
        }
    }

Konstruktor tej klasy wymaga podania nazwy klasy betonu oraz modułu Younga. Tylko, że w 99% przypadków wartość modułu Younga jest skorelowana jeden-do-jeden z nazwą klasy betonu. Podobnie kolejne parametry opisujące zachowanie fizyczne betonu – wytrzymałość na ściskanie, rozciąganie, itd. Rozwijając nasz program o kolejne funkcjonalności wymagające sprecyzowania parametrów betonu szybko doszlibyśmy do konstruktora wymagającego >10 różnych wartości. Tworzenie takich obiektów (czysto ludzko) jest koszmarem – bardzo łatwo jest pomylić kolejnosć argumentów. A i tak te wszystkie parametry wynikają z zadanej klasy betonu – dokładnie jak w rozmowie z operatorem węzła betoniarskiego.

Dlatego proponuję wykorzystać inżynierskie zależności z Eurokodu i zmodyfikować naszą klasę Concrete. Spójrz co naklepałem:

    public class Concrete
    {
        public string Name { get; private set; }
        public double Fck { get; private set; }
        public double Fck_cube { get; private set; }
        public double Fcm { get; private set; }
        public double Fctm { get; private set; }
        public double ElasticModulus { get; private set; }

        public Concrete(string name)
        {
            Name = name;

            InitalizeConcreteProperties();
        }

        private void InitalizeConcreteProperties()
        {
            switch (Name)
            {
                case "C12/15":
                    Fck = 12 * Math.Pow(10, 6);
                    Fck_cube = 15 * Math.Pow(10, 6);
                    break;
                default:
                    throw new ArgumentNullException(nameof(Name), "Provided concrete class name is not allowed");
            }

            Fcm = Fck + 8 * Math.Pow(10, 6);

            Fctm = (Fck <= 50 * Math.Pow(10, 6)) ?
                0.30 * Math.Pow(Fck * Math.Pow(10, -6), 2 / 3.0) :
                2.12 * Math.Log(1 + Fcm * Math.Pow(10, -6) / 10);

            ElasticModulus = 22 * Math.Pow((Fcm * Math.Pow(10, -6)) / 10, 0.3) * Math.Pow(10, 9);
        }

        //https://chodor-projekt.net/encyclopedia/pelzanie-skurcz-betonu/#(33)
        public double CalculateEffectiveElasticModulus(double creepCoefficient)
        {
            return ElasticModulus / (1 + creepCoefficient);
        }
    }

Co najbardziej rzuca Ci się w oczy? Mnie strasznie kłują te mnożenia przez Math.Pow(10, 6) i odwrotne Math.Pow(10, -6). Normowe wzory w większości wymagają podawania wartości w określonych jednostkach – nie wystarczy rzucić im wartości w paskalach. Czasami wzór został tak skonstruowany, że tylko podanie wartości w MPa da oczekiwany rezultat.

Tak było, jest i będzie – musimy się dostosować. Klasycznym rozwiązaniem byłoby przyjęcie, że operować będziemy na układzie SI i każdorazowo będziemy zamieniać podstawowe wartości na ich wersje z kilo/mega/giga – patrz przykład powyżej. Do tego piękny opis poszczególnych argumentów metod połączony ze wskazaniem oczekiwanej jednostki i mamy czyste ręce. Tylko, że prędzej czy później pomylimy się, podamy gdzieś formę w Pa zamiast w MPa, metody zaczną zwracać bzdurne wyniki. Samo patrzenie na taki kod powoduje, że podskórnie czujesz, że gdzieś tam czai się babol. Aż chciałoby się mieć magiczną różdżkę, która będzie na zawołanie dokonywać konwersji pomiędzy poszczególnymi jednostkami.

Zacznijmy więc tworzyć odpowiednie klasy potrafiące zamieniać … Wait, daj sobie na wstrzymanie. Zanim zaczniesz klepać nową funkcjonalność spójrz z boku na swój problem i zadaj fundamentalne pytanie: czy to ja jestem taki wyjątkowy czy jednak z tym zagadnieniem ktoś wcześniej się zetknął i może go rozwiązął? Odpowiedzi będą różne, ale warto pytać, zwłaszcza samego siebie.

Problem konwersji jednostek jest powszechnie znany, a co najlepsze – dostępne jest rozwiązanie, które weźmiemy z półki i wstrzykniemy do naszego programu. Przy kasie zobaczymy kwotę 0 zł – skorzystamy z biblioteki na licencji MIT, która umożliwia, wspiera dalsze wykorzystanie efektu pracy innych programistów. Pora poznać UnitsNet.

Ok, link wygląda na pierwszy rzut oka strasznie. Nie musisz analizować jak zbudowana jest ta biblioteka, jak ją skompilować – chcemy ją szybko i bezboleśnie wykorzystać w naszym programie. W tym celu naciśnij PPM na References i wybierz Manage NuGet Packages.

Witaj w królestwie paczek, które czekają na skosztowanie w Twoich kolejnych projektach. Chcemy spróbować paczki UnitsNet, więc wpisz proszę tę nazwę w polu wyszukiwania będąc w zakładce Browse. Po wybraniu paczki wybierz Install dostępne po prawej stronie.

Po zakończeniu procesu instalacji paczka jest gotowa do używania. W tym celu musimy rozszerzyć zakres poszukiwania klas dodając nową przestrzeń nazw, w tym wypadku using UnitsNet; Spójrz jak teraz możemy zapisać szkielet klasy Concrete:

using System;
using UnitsNet;

namespace ConcreteLibrary
{
    public class Concrete
    {
        public string Name { get; private set; }
        public Pressure Fck { get; private set; }
        public Pressure Fck_cube { get; private set; }
        public Pressure Fcm { get; private set; }
        public Pressure Fctm { get; private set; }
        public Pressure ElasticModulus { get; private set; }

        public Concrete(string name)
        {
            Name = name;

            InitalizeConcreteProperties();
        }

        private void InitalizeConcreteProperties()
        {
            switch (Name)
            {
                case "C12/15":
                    Fck = Pressure.FromMegapascals(12);
                    Fck_cube = Pressure.FromMegapascals(15);
                    break;
                default:
                    throw new ArgumentNullException(nameof(Name), "Provided concrete class name is not allowed");
            }

            Fcm = Fck + Pressure.FromMegapascals(8);

            Fctm = Pressure.FromMegapascals(
                (Fck.Megapascals <= 50) ?
                0.30 * Math.Pow(Fck.Megapascals, 2 / 3.0) :
                2.12 * Math.Log(1 + Fcm.Megapascals / 10));

            ElasticModulus = Pressure.FromGigapascals(22 * Math.Pow(Fcm.Megapascals / 10, 0.3));
        }

        //https://chodor-projekt.net/encyclopedia/pelzanie-skurcz-betonu/#(33)
        public double CalculateEffectiveElasticModulus(double creepCoefficient)
        {
            return ElasticModulus.Pascals / (1 + creepCoefficient);
        }
    }
}

Zniknęły magiczne zamiany z Pa na MPa i na odwrót. Zamiast mnożenia przez wielokrotności 10-ciu mamy zgrabną definicję:

Pressure Fck = Pressure.FromMegapascals(12);

Patrząc na to od razu czujesz co się dzieje w danej linijce. Podobnie jak wskazanie w jakiej jednostce chcesz uzyskać wartość, o patrz:

double valueInMegapascals = Fck.Megapascals;

Życie stało się zdecydowanie prostsze. Jeszcze jeżeli dodamy odczytywanie podstawowych wytrzymałości na ściskanie na bazie nazwy podanej przez użytkownika to całość klasy betonu zamknie się w poniższym kodzie:

using System;
using System.Linq;
using UnitsNet;

namespace ConcreteLibrary
{
    public class Concrete
    {
        public string Name { get; private set; }
        public Pressure Fck { get; private set; }
        public Pressure Fck_cube { get; private set; }
        public Pressure Fcm { get; private set; }
        public Pressure Fctm { get; private set; }
        public Pressure ElasticModulus { get; private set; }

        public Concrete(string name)
        {
            Name = name;

            InitalizeConcreteProperties();
        }

        private void InitalizeConcreteProperties()
        {
            bool initialized = false;
            if (Name.StartsWith("C") && Name.Count(x => x == '/') == 1)
            {
                string[] splitted = Name.Split('/');
                int fckFromName;
                int fckCubeFromName;

                if (int.TryParse(splitted[0].Substring(1), out fckFromName) &&
                    int.TryParse(splitted[1], out fckCubeFromName))
                {
                    Fck = Pressure.FromMegapascals(fckFromName);
                    Fck_cube = Pressure.FromMegapascals(fckCubeFromName);

                    initialized = true;
                }
            }

            if (!initialized)
            {
                throw new ArgumentNullException(nameof(Name), "Provided concrete class name is not allowed");
            }

            Fcm = Fck + Pressure.FromMegapascals(8);

            Fctm = Pressure.FromMegapascals(
                (Fck.Megapascals <= 50) ?
                0.30 * Math.Pow(Fck.Megapascals, 2.0 / 3.0) :
                2.12 * Math.Log(1 + Fcm.Megapascals / 10.0));

            ElasticModulus = Pressure.FromGigapascals(22 * Math.Pow(Fcm.Megapascals / 10.0, 0.3));
        }

        //https://chodor-projekt.net/encyclopedia/pelzanie-skurcz-betonu/#(33)
        public double CalculateEffectiveElasticModulus(double creepCoefficient)
        {
            return ElasticModulus.Pascals / (1 + creepCoefficient);
        }
    }
}

Linijki od 26 do 40 to zamiana nazwy klasy betonu na dwie podstawowe charakterystyki, które później przy pomocy normowych zależności zamieniane są na pozostałe zmienne opisujące nasz beton.

Z tego posta chcę żebyś zapamiętał jedną podstawową różnicę pomiędzy światem inżynierów a programistów – w tym drugim dzielenie się wiedzą jest standardem, również w postaci udostępniania za darmo wartościowych paczek, z których możesz skorzystać. Wzajemna pomoc nakręca rozwój – dopóki do gry nie włączy się dział finansów, ale to opowieść na inną historię.

Categories
C#

Moment bezwładności przekroju niezarysowanego

Wpis [part not set] z 8 w serii C# - podstawy

Poznaliśmy koncept klas i obiektów, udało nam się dotychczas wyróżnić dwie klasy: ConcreteSection oraz Rebar. Jednak poza definicją klasy Rebar nie zaliczyliśmy znacznego postępu. Naszym celem jest stworzenie programu wyznaczającego moment bezwładności prostokątnego przekroju niezarysowanego. Podstawy teoretyczne pochodzą ze strony https://chodor-projekt.net/ – ogromne podziękowania dla Pana Leszka Chodora za dzielenie się wiedzą!

Nasze zadanie zahacza o pojęcie ‘sprowadzonego pola przekroju’. Mamy betonowy przekrój prostokątny w którym znajdują się pręty stalowe, wykonane z innego materiału, posiadające znacznie większy moduł spreżystości niż otaczający je beton. Faza niezarysowana jest o tyle przyjemna, że możemy przymknąc oko na mikroświat i przyjąć w trakcie obliczeń, że miejsce zajmowane przez stal jest odpowiednio sztywniejsze od betonu, do którego chcemy ją sprowadzić. W tym celu będziemy potrzebować współczynnika ‘zamieniającego’ stal w beton.

    \[ \alpha_e=\frac{E_s}{E_{c,eff}}\]

Powyższa czarodziejska różdżka potrzebuje do działania modułu sprężystości stali i efektywnego modułu sprężystości betonu. Hmm, czyli nie wystarczy nam wiedza jaką nazwę nosi zastosowana klasa betonu i stali – potrzebujemy większej liczby właściwości opisujących te ‘byty’. Moment, w którym okazuje się, że do opisania danej cechy nie wystarczy jedna liczba lub słowo to czas na wydzielenie nowej klasy.

Tylko zanim się rozpędzimy wykonajmy jeszcze jedną czynność porządkową – utwórzmy nowy projekt, który będzie biblioteką klas, czyli czymś, czego nie da się samodzielnie uruchomić poprzez plik .exe ale będzie w przyszłości stanowić część większych programów. Z poziomu menu File wybierz Add -> New Project a następnie wskaż nowy typ projektu Class Library (.NET Framework). Nazwijmy go ConcreteLibrary.

Utworzenie nowego projektu i przekazanie mu odpowiedzialności za nową, samodzielną funkcjonalność jest ważną czynnością porządkową – w naszym wypadku umożliwi to sprawne wykorzystanie algorytmu w połączeniu z programami budowlanymi.

W ramach projektu ConcreteLibrary utwórz nowe klasy materiałowe: Steel oraz Concrete. Na użytek tego zadania proponuję coś na kształt:

    public class Steel
    {
        public string Name { get; set; }
        public double ElasticModulus { get; set; }

        public Steel(string name, double elasticModulus)
        {
            Name = name;
            ElasticModulus = elasticModulus;
        }
    }
    public class Concrete
    {
        public string Name { get; set; }
        public double ElasticModulus { get; set; }

        public Concrete(string name, double elasticModulus)
        {
            Name = name;
            ElasticModulus = elasticModulus;
        }

        public double CalculateEffectiveElasticModulus(double creepCoefficient)
        {
            return ElasticModulus / (1 + creepCoefficient);
        }
    }

Klasa Concrete jest w stanie obliczyć wartość efektywnego modułu sprężystości, uwzględniając wskazany współczynnik pełzania.

Mając tak zdefiniowane podstawowe klasy materiałowe musimy zmodyfikować dotychczasowe ConcreteSection oraz Rebar tak, żeby korzystały z dobrodziejstw nowych klas (początkowo przechowywaliśy tylko nazwy użytych materiałów):

    public class ConcreteSection
    {
        public double Width { get; set; }
        public double Height { get; set; }
        public Concrete ConcreteClass { get; set; }

        public ConcreteSection(double width, double height, Concrete concreteClass)
        {
            Width = width;
            Height = height;
            ConcreteClass = concreteClass;
        }

        public double CalculateArea()
        {
            return Width * Height;
        }

        public double CalculateFirstMomentOfArea()
        {
            return CalculateArea() * Height / 2.0;
        }

        public double CalculateMomentOfInertia()
        {
            return (Width * Math.Pow(Height, 3)) / 12;
        }
    }

Klasa Rebar zaprezentowana poniżej zyskała dodatkową moc, potrafi teraz wyznaczyć odległość do górnej krawędzi przekroju. Konstruktor klasy oczekuje podania odległości od dolnej krawędzi, z kolei żelbetowe algorytmy wykorzystują czasami domiar od góry.

public class Rebar
{
    public double Diameter { get; set; }
    public Steel SteelClass { get; set; }
    public double DistanceFromBottomEdge { get; set; }

    public Rebar(double diameter, Steel steelClass, double distanceFromBottomEdge)
    {
        Diameter = diameter;
        SteelClass = steelClass;
        DistanceFromBottomEdge = distanceFromBottomEdge;
    }

    public double CalculateArea()
    {
        return Math.PI * Diameter * Diameter / 4.0;
    }

    public double CalculateDistanceFromTopEdge(double sectionHeight)
    {
        return sectionHeight - DistanceFromBottomEdge;
    }
}

Przekrój żelbetowy składa się z wielu prętów – obiektów klasy Rebar. Musimy zastanowić się w jaki sposób będziemy przechowywać o nich informacje. Istnieją dwie podstawowe możliwości:

  • zbierać pręty w ramach klasy ReinforcedSection, która będzie również finalnie obliczać moment bezwładności,
  • stworzyć nową klasę, której głównym zadaniem będzie agregowanie informacji o prętach w przekroju.

Innymi słowami: w przyszłości wielokrotnie będziesz stawał przed dylematem gdzie umiejscawiać nowe funkcjonalności – upychać je w istniejącej strukturze klas czy zatrzymać się na chwilę, wydzielić nowe klasy oraz powiązania pomiędzy nimi. Jeżeli goni Cię deadline, to zdecydujesz się na zwiększanie już posiadanych klas – będzie szybciej. Ucierpi na tym zarządzalność projektem w przyszłości – trudniej będzie dodać nowości, trudniej będzie zrozumieć powody błędnego działania. Ale … dostarczyłeś nową funkcjonalność na czas – ot, klasyczny trade-off programisty.

Mamy czas, stwórzmy zatem nową klasę: RebarsInSection:

public class RebarsInSection : IEnumerable<Rebar>
    {
        private List<Rebar> _rebars = new List<Rebar>();

        public Rebar this[int index]
        {
            get { return _rebars[index]; }
            set { _rebars.Insert(index, value); }
        }

        public Steel GetSteelClass()
        {
            if (_rebars.Count > 0)
                return _rebars.First().SteelClass;
            else
                return null;
        }

        public void AddRebar(Rebar rebar)
        {
            _rebars.Add(rebar);
        }

        public double CalculateArea()
        {
            return _rebars.Sum(x => x.CalculateArea());
        }

        public IEnumerator<Rebar> GetEnumerator()
        {
            return _rebars.GetEnumerator();
        }

        IEnumerator IEnumerable.GetEnumerator()
        {
            return _rebars.GetEnumerator();
        }
    }

Powyższa klasa zawiera zupełnie nowy element – po podaniu jej nazwy wpisałem : IEnumerable<Rebar>. To deklaracja implementacji interfejsu, czyli rodzaju umowy pomiędzy róznymi klasami gwarantujących sprawną współpracę. IEnumerable to podstawowy interfejs umożliwiający wyliczanie zgrupowanych obiektów – w naszym wypadku prętów. Metody GetEnumerator() musieliśmy zaimplementować w związku z wykorzystaniem tego interfejsu – niewielki koszt biorąc pod uwagę płynącego z tego zyski, o nich za chwilę.

Klasa RebarsInSection zawiera pole _rebar będące listą prętów. To w niej będziemy przechowywać pręty w przekroju. Dodatkowo mamy tu kilka metod pomocniczych, które przydadzą się w naszej finalnej klasie: ReinforcedSection, która połączy wszystkie fragmenty układanki.

    public class ReinforcedSection
    {
        private ConcreteSection _concreteSection;
        private RebarsInSection _rebars;

        public ReinforcedSection(ConcreteSection concreteSection, RebarsInSection rebars)
        {
            _concreteSection = concreteSection;
            _rebars = rebars;
        }

        public double CalculateMomentOfInertia()
        {
            double cogFromTopEdge = CalculateCenterOfGravityFromTopEdge();
            double steelToConcreteCoefficient = CalculateSteelToConcreteCoefficient();

            double inertia = _concreteSection.CalculateMomentOfInertia();
            inertia += _concreteSection.CalculateArea() * Math.Pow(_concreteSection.Height / 2 - cogFromTopEdge, 2);

            foreach (Rebar rebar in _rebars)
            {
                double distanceToCog = rebar.CalculateDistanceFromTopEdge(_concreteSection.Height) - cogFromTopEdge;
                inertia += rebar.CalculateArea() * steelToConcreteCoefficient * Math.Pow(distanceToCog, 2);
            }

            return inertia;
        }

        private double CalculateCreepCoefficient()
        {
            return 1.5;
        }

        private double CalculateSteelToConcreteCoefficient()
        {
            if (_rebars.Count() == 0)
                return 1;

            double creepCoefficient = CalculateCreepCoefficient();
            double concreteEffectiveModulus = _concreteSection.ConcreteClass.CalculateEffectiveElasticModulus(creepCoefficient);

            double steelModulus = _rebars.GetSteelClass().ElasticModulus;

            return steelModulus / concreteEffectiveModulus;
        }

        private double CalculateAreaOfConcreteEquivalentSection()
        {
            double area = _concreteSection.CalculateArea();
            area += _rebars.CalculateArea() * CalculateSteelToConcreteCoefficient();

            return area;
        }

        private double CalculateFirstMomentOfAreaFromTopEdge()
        {
            var firstMomentOfArea = _concreteSection.CalculateFirstMomentOfArea();

            double steelToConcreteCoefficient = CalculateSteelToConcreteCoefficient();
            foreach (Rebar rebar in _rebars)
            {
                double distanceFromTopEdge = rebar.CalculateDistanceFromTopEdge(_concreteSection.Height);
                firstMomentOfArea += rebar.CalculateArea() * steelToConcreteCoefficient * distanceFromTopEdge;
            }

            return firstMomentOfArea;
        }

        private double CalculateCenterOfGravityFromTopEdge()
        {
            return CalculateFirstMomentOfAreaFromTopEdge() / CalculateAreaOfConcreteEquivalentSection();
        }
    }

Ok, tu zaczyna się coś dziać, przeanalizujmy to od góry:

  • dwa pola: _concreteSection oraz _rebars inicjalizowane w konstruktorze,
  • publiczna metoda CalculateMomentOfInertia(), która dyryguje pomniejszymi metodami
  • zbiór metod prywatnych, czyli takich do których dostęp jest tylko w ramach klasy, które nie są dostępne z poziomu innych klas.

Na użytek tej części zdecydowałem się na uproszczenie – metoda CalculateCreepCoefficient() zwraca zawsze wartość 1.5. Nie chciałem dalej rozbudowywać programu uwzględniająć różne sytuacje, czasy i zależności od wymiaru charakterystycznego przekroju – przyjdzie na to czas.

Wydzielenie do nowego projektu pełnej funkcjonalności rodzi pytanie w jaki sposób wykorzystać ją w innych projektach. Programowanie bardzo często polega na sklejaniu różnych bibliotek w celu uzyskania nowego produktu. W naszym wypadku do dyspozycji mamy projekt/bibliotekę ConcreteLibrary. Wykorzystanie jej w początkowym projekcie PierwszaKonsola musimy zaczać od dodania odpowiedniej referencji. Prawym przyciskiem myszy kilknij na References w drzewie projektu do którego chcesz dodać nowe odniesienie.

W nowym oknie dialogowym ‘zaptaszkuj’ wzorcowy projekt, który chcesz wykorzystać:

Ostatnim krokiem do wykorzystania gotowej funkcjonalności jest wskazanie jakie przestrzenie nazw będa wykorzystywane w ramach danego pliku źródłowego. W pliku Program.cs, który jest punktem wejście w projekcie PierwszaKonsola musimy na samej górze dodać using ConcreteLibrary; , co podpowie kompilatorowi jakiej przestrzeni nazw będziemy używać. Kompletna wersja Program.cs przedstawiająca jak korzystać z biblioteki w innym projekcie znajduje się poniżej:

using System;
using ConcreteLibrary;

namespace PierwszaKonsola
{
    public class Program
    {
        public static void Main()
        {
            Concrete concreteClass = new Concrete("C30/37", 34 * Math.Pow(10, 9));
            Steel steelClass = new Steel("B500SP", 200 * Math.Pow(10, 9));

            double width = 0.25;
            double height = 0.50;
            ConcreteSection section = new ConcreteSection(width, height, concreteClass);

            double diameter = 0.016;
            double cover = 0.030;
            Rebar rebar = new Rebar(diameter, steelClass, cover + diameter / 2);

            RebarsInSection rebars = new RebarsInSection();
            rebars.AddRebar(rebar);

            ReinforcedSection reinforcedSection = new ReinforcedSection(section, rebars);

            string message1 = string.Format("Moment bezwładności wynosi {0:N8}", reinforcedSection.CalculateMomentOfInertia());
            Console.WriteLine(message1);
            Console.ReadLine();
        }
    }
}

Mam kilka zarzutów co do wygody stosowania tej biblioteki. Klasy betonu czy stali powinny miec możliwość automatycznego uzupełniania wartości poszczególnych parametrów – zajmiemy się tym w kolejnej części. Na dziś wystarczy nowości 🙂

Categories
C#

Wprowadźmy klasy

Wpis [part not set] z 8 w serii C# - podstawy

Nie, nie, nie – spokojnie, nie zamierzam wchodzić w tematykę społeczną, a na pewno nie w tym miejscu. Chcę za to dodać do naszego prostokątnego przekroju stalowe pręty, żebyśmy wreszcie mogli zacząć liczyć coś sensownego. W tym celu musimy porzucić dotychczasowy sposób pisania – nie sprostałby wymagań rosnącej funkcjonalności.

Jeżeli zetknąłeś się z programowaniem w ramach studiów budowlanych to prawdopodobnie klepałeś kod w myśl paradygmatu imperatywnego, gdzie program to ciąg sekwencji wykonywanych jedna po drugiej, zgodnie z założeniami programisty. Choć wątpię żebyś zastanawiał się nad istotą tej pracy – raczej chciałeś jak najszybciej skończyć zajęcia, żeby zająć się tym co wtedy wydawało się ważniejsze.

C# ma wytuatuowane na czole jestem obiektowy i choć to za słaba podpowiedź do gry ‘zgadnij kim jestem’ to warto zrozumieć na podstawowym poziomie koncept obiektowości – będziemy z niego korzystać przez cały czas. Postaram się go przedstawić na przykładzie naszego zadania – przekroju prostokątnego ze zbrojeniem.

Patrząc na jeden, konkretny przekrój w środku przęsła belki widzimy jeden prostokąt i kilka/kilkanaście prętów. W jaki sposób opisujemy te cześci składowe? Zacznijmy od przypomnienia poprzedniej części, czyli opiszmy przekrój.

Betonowy przekrój prostokątny w zakresie wyznaczania momentu bezwładności:

  • ma szerokość,
  • ma wysokość,
  • ma klasę betonu,
  • zwraca pole powierzchni,
  • zwraca moment bezwładności.

Co to za zabawy linigwistyczne zapytasz. Język programowania to właśnie język, kierujący się określoną logiką. Jak to będzie wyglądało w przypadku pręta stalowego w przekroju:

  • ma średnicę,
  • ma klasę stali,
  • ma położenie, lokalizację w ramach przekroju,
  • zwraca pole powierzchni.

Z premedytacją używałem dwóch czasowników: ma oraz zwraca.

  • Ma to podstawowa wartość, która określa, doprecyzowuje nasz element, która jest właściwością.
  • Zwraca to wyróżnik czegoś wynikowego, uzyskanego z operacji matematycznych na właściwościach lub ‘umiejętność’ danego obiektu. Zwraca to moje określenie metody, czyli funkcji, która coś robi.

Tak patrzę na betonowy przekrój oraz pręt w nim zawarty i trudno mi znaleźć cechy wspólne. Oba mogą zwrócić pole powierzchni, ale to trochę za mało, żeby wrzucić je do jednego worka – to są dwa oddzielne, logiczne byty. Fajnie by było zaimplementować nasz opis i właśnie w tym celu potrzebujemy konceptu klasy, czyli definicji jakiegoś elementu, obiektu. Spójrz na nową klasę ConcreteSection:

    public class ConcreteSection
    {
        // Properties
        public double Width { get; set; }
        public double Height { get; set; }
        public string ConcreteClass { get; set; }

        // Methods
        public double GetArea()
        {
            return Width * Height;
        }
        public double GetMomentOfInertia()
        {
            return (Width * Math.Pow(Height, 3)) / 12;
        }
    }

A jak by wyglądała klasa pręta stalowego?

    public class Rebar
    {
        // Properties
        public double Diameter { get; set; }
        public string SteelClass { get; set; }
        public double Location { get; set; }

        // Methods
        public double GetArea()
        {
            return Math.PI * Diameter * Diameter / 4.0;
        }
    }

Klasa to taki stempelek z ziemniaka określający kształt utworzonych przy jej pomocy obiektów. Definicja danej klasy jest jedna, natomiast obiektów powstałych przy jej pomocy – jeden/wiele/wedle potrzeb. Mając ziemniaczany stempelek możesz narobić tyle odbitek na ile wystarczy Ci tuszu i kartek/ścian, mając klase możesz stworzyć tyle obiektów na ile wystarczy Ci pamięci RAM w Twoim komputerze, czyli duuuuuużo.

Tylko jak stworzyć nowe odbitki z naszych pieczątek? Służy do tego słowo kluczowe new, spójrz proszę:

public static void Main()
{
    ConcreteSection section = new ConcreteSection();
    Rebar rebar = new Rebar();
}

Zastosowanie new wyraża intencję programisty – stwórz nowy obiekt danej klasy. Powyższa inicjalizacja nie jest powiązana z przypisaniem wartości do nowych obiektów – w konstruktorze klasy nie przekazujemy żadnych wartości. Mamy zwykłe ConcreteSection section = new ConcreteSection();

Konstruktor to wyjątkowa metoda/funkcja, której możemy użyć do początkowego ustawienia nowo powstałego obiektu. Na chwilę obecną nasze obiekty są golutkie, spójrz:

Brak klasy betonu, brak wysokości, brak szerokości. Próba wywołania metod obliczających pole powierzchni lub moment bezwładności zwróci zero, może to nie tragedia, ale już null’owa wartość klasy betonu może skończyć się wysypaniem programu. Nasze obiekty są zakalcowate, niezdatne do bezpiecznego spożycia. Istnieje kilka szkół inicjalizacji obiektów, my zrobimy to w bezpieczny sposób, oczekując podania wszystkich właściwości na etapie tworzenia nowych instancji klas. To dobry moment, żebyś dodał do swojego projektu nowe klasy. W tym celu naciśnij PPM na nazwie Twojego projektu widocznego w oknie po prawej stronie Visual Studio, rozwiń opcję Add i wybierz Class. Alternatywnie możesz skorzystać ze skrótu Shift + Alt + C.

W nowym oknie wpisz nazwę klasy: ConcreteSection, dodaj rozszerzenie pliku .cs i wybierz Add.

Pora na napisanie definicji klasy, proponuję żeby wyglądała następująco:

using System;

namespace PierwszaKonsola
{
    public class ConcreteSection
    { 
        // Properties
        public double Width { get; set; }
        public double Height { get; set; }
        public string ConcreteClass { get; set; }

        // Constructor
        public ConcreteSection(double width, double height, string concreteClass)
        {
            Width = width;
            Height = height;
            ConcreteClass = concreteClass;
        }

        // Methods
        public double GetArea()
        {
            return Width * Height;
        }
        public double GetMomentOfInertia()
        {
            return (Width * Math.Pow(Height, 3)) / 12;
        }
    }
}

Dodaj kolejną klasę do projektu, tym razem o nazwie Rebar:

using System;

namespace PierwszaKonsola
{
    public class Rebar
    {
        // Properties
        public double Diameter { get; set; }
        public string SteelClass { get; set; }
        public double Location { get; set; }

        // Constructor
        public Rebar(double diameter, string steelClass, double location)
        {
            Diameter = diameter;
            SteelClass = steelClass;
            Location = location;
        }

        // Methods
        public double GetArea()
        {
            return Math.PI * Diameter * Diameter / 4.0;
        }
    }
}

Mając zdefiniowane w projekcie dwie klasy zastąp proszę zawartość pliku Program.cs poniższą treścią:

using System;

namespace PierwszaKonsola
{
    public class Program
    {
        public static void Main()
        {
            ConcreteSection section = new ConcreteSection(30, 60, "C30/37");

            Console.WriteLine(section.Height);
            Console.ReadLine();
        }
    }
}

Program wyświetli w konsoli wartość 60. Może Cię kusić żeby stosować tę technikę podglądania obiektów częściej, ale nie rób tego. Zamiast wyświetlania w konsoli wartości, które Cię interesują spraw, aby program wstrzymał wykonywanie w zadanym przez Ciebie punkcie. W tym celu musisz zdefiniowac punkt debugowania, najprościej poprzez najechanie kursorem myszy na wyszarzony pasek po lewej stronie edytora:

i kliknięcie LPM – w tym momencie kropka stanie się czerwona. Uruchom teraz Twój program z poziomu VS – naciśnij F5.

W konsoli nie ma oczekiwanej wartości wysokości przekroju, co więcej focus przeszedł na Visual Studio, które mruga zachęcająco wskazując linijkę, przed którą zatrzymane zostało wykonywanie:

Właśnie wkroczyłeś w świat debugowanie, czyli podglądania w jakim stanie są obiekty w danym momencie wykonywania. To standardowe narzędzie potrzebne do zrozumienia i wyeliminowania bugów, czyli błędów w aplikacji. Zetknąłeś się z naprawdę potężną mocą, której pełne opanowanie chwilę potrwa. Na ten moment wiedz, że podstawowe opcje znajdują się na górnym pasku VS:

W obecnej sytuacji interesuje nas przede wszystkim przechodzenie do kolejnej linijki, które najłatwiej zrealizować poprzez naciśnięcie F10. Aplikacja przejdzie wtedy do kolejnej linijki kodu a my będziemy mogli sprawdzić efekty wykonania poprzedniej. Właśnie w ten sposób upewniłem się, że konstruktor poprawnie przekazał argumenty do właściwości naszego obiektu – można z niego korzystać.

Hej, pamiętasz nasze poprzednie podejście do konsoli? To w którym pobieraliśmy od użytkownika szerokość i wysokość przekroju a następnie obliczaliśmy i wyświetlaliśmy moment bezwładności. Tamto podejście łamało zasadę ograniczania odpowiedzialności mieszając w jednej klasie różne bajki – pobieranie danych od użytkownika i wyznaczanie momentu bezwładności. Spójrz na zmodyfikowany przykład wykorzystujący utworzoną dzisiaj klasę ConreteSection:

using System;

namespace PierwszaKonsola
{
    public class Program
    {
        public static void Main()
        {
            Console.WriteLine("Podaj szerokość przekroju:");
            string widthFromUser = Console.ReadLine();
            double? width = GetDoubleFromUser(widthFromUser);
            if (!width.HasValue)
                return;
            Console.WriteLine("Podaj wysokość przekroju:");
            string heightFromUser = Console.ReadLine();
            double? height = GetDoubleFromUser(heightFromUser);
            if (!height.HasValue)
                return;

            ConcreteSection section = new ConcreteSection(width.Value, height.Value, "C30/37");

            string message1 = string.Format("Moment bezwładności wynosi {0:N2}", section.GetMomentOfInertia());
            Console.WriteLine(message1);
            Console.ReadLine();
        }

        public static double? GetDoubleFromUser(string inputValue)
        {
            double result;
            if (double.TryParse(inputValue, out result))
                return result;
            else
            {
                Console.WriteLine($"Wartość {inputValue} nie może zostać zamieniona na liczbę. Koniec działania programu.");
                return null;
            }
        }
    }
}

Jest lepiej, oddelegowaliśmy wyznaczanie momentu bezwładności odpowiedniemu obiektowi zapewniając wczesniej, że będzie on posiadał komplet danych do wykonania działania (konstruktor przekazujący wartości). I tak będziemy robić w dalszych częściach – wykorzystamy naszą wiedzę domenową w celu nazwania, zdefiniowania obiektów, których potrzebujemy, określimy ich właściwości oraz umiejętności (metody) a następnie połączymy stosownie do potrzeb w elegancką aplikację. No, może czasami to będzie elegancja ciosana udarowym Bosh’em, ale hej – pamiętasz gdzie byłeś przed stworzeniem swojej pierwszej aplikacji konsolowej?

Categories
C#

Czarne jak konsola

Wpis [part not set] z 8 w serii C# - podstawy

Jeżeli po poprzednim poście męczyło Cię pytanie “po co właściwie zainstalowałem to Visual Studio?” to już piszę odpowiedź. Chociaż, w sumie, wolę żebyś sam to sprawdził – uruchom proszę na Twoim komputerze Visual Studio 2022. Po prawej stronie ekranu startowego znajdziesz przycisk Create a new project – wskaż go.

Pora na wybranie szablonu projektu. Kurczę, czego? Visual Studio oferuje Ci szereg predefiniowanych “rusztowań” projektów, które możesz zapełniać potrzebną Ci zawartością. Naszą przygodę zaczniemy od aplikacji konsolowej, czyli czegoś co spełnia poniższe cechy:

  • jest kuszące niczym Polski Ład,
  • ma rozszerzenie .exe, jest executable, czyli samowykonywalne,
  • oferuje podstawową interakcję z użytkownikiem, wyświetlając znaki na ekranie oraz przyjmując wartości od operatora,
  • standardowo ma przepiekny, głęboki, czarny kolor tła 😛

W polu wyszukiwania wpisz console app i wybierz wariant dla C# w .NET Framework.

Jeżeli dziwisz się dlaczego są dwa warianty templatu dla aplikacji konsolowej w C# to na ten moment wiedz, że wybieramy starszy sposób, z którego korzystają wszystkie programy budowlane. Jak to zwykle bywa – innowacje przybywają do nas z ‘lekkim’ opóźnieniem.

Ok, wybrałeś Next, podałeś nazwę projektu (np. PierwszaKonsola), jego lokalizację i kliknałeś w Create. Po chwili zwątpienia widzisz w pełnej krasie swój pierwszy projekt w C# – brawo Ty! Pora uruchomić program, naciśnij zachęcający, zielony grot z napisem Start.

Hmm, u Ciebie też coś czarnego pojawiło się na ekranie i szybko zniknęło? Spójrz na zawartość naszego programu, czyli obszar pomiędzy klamerkami między 12 a 13 linijką. Niczego tam nie ma i to z tego powodu nasz program bardzo szybko kończy pracę. Wpisz proszę jakieś polecenie, np. próbę wyświetlenia na ekranie tekstu, o choćby tego:

        static void Main(string[] args)
        {
            Console.WriteLine("Let's construct IT");
        }

Naciśnij znów zielony grot – chcemy sprawdzić jak teraz zadziała nasz program. Kurczę, znowu okno pojawia się i znika, a to przecież rola magika. Czegoś tu brakuje, jakiejś formy oczekiwania na interakcję użytkownika. Zmodyfikuj wnętrze metody Main, tak żeby poczekała na ruch operatora, coś na kształt tego:

    static void Main(string[] args)
    {
        Console.WriteLine("Let's construct IT");
        Console.ReadLine();
    }

Uruchom program (F5 jest skrótem dla zielonego grota, będzie szybciej) – jeeeeeest, działa! To jest moment na zawołanie żony/męża/mamy/kota i pokazanie, że potrafisz programować, a przynajmniej jesteś na dobrej drodze.

Żeby zamknąć naszą aplikację możesz użyć krzyżyka lub wykonać to, czego oczekuje linijka Console.ReadLine() czyli wpisać jakiekolwiek znaki i nacisnąć enter.

Pamiętasz kod obliczający moment bezwładności prostokąta z wcześniejszych postów? Nie? Ja też nie pamiętałem, dlatego wklejam go poniżej. Proszę skopiuj go i wklej u siebie, zastąpując to wszystko co widzisz w edytorze Visual Studio.

using System;

public class Program
{
    public static void Main()
    {
        double momentOfInertia1 = GetMomentOfInertia(30, 60);
        double momentOfInertia2 = GetMomentOfInertia(20, 50);

        string message1 = string.Format("Moment bezwładności 1 wynosi {0:N2}", momentOfInertia1);
        Console.WriteLine(message1);
        string message2 = string.Format("Moment bezwładności 2 wynosi {0:N2}", momentOfInertia2);
        Console.WriteLine(message2);

        Console.ReadLine();
    }

    public static double GetMomentOfInertia(double b, double h)
    {
        double momentOfInertia = (b * Math.Pow(h, 3)) / 12;

        return momentOfInertia;
    }
}

Uruchom nowy program, najlepiej klawiszem F5. W konsoli wyświetlone zostały dwie wartości momentu bezwładności – nasz program działa.

Gdy przedstawiałem ten przykład w Fiddle wspomniałem, że posiada on zaszyte na sztywno wartości dla których wykonane mają zostać działania. Chcemy tworzyć programy elastyczne, skrojone pod potrzeby użytkowników i właśnie teraz nadszedł czas na implementację nowej funkcji – pobierania od operatora szerokości i wysokości prostokąta.

Na samym początku metody Main dopisz na szybko linijkę Console.ReadLine(); i skieruj kursor myszy na słowo ReadLine – Visual Studio podpowie Ci co robi ta metoda oraz jaki typ zwraca. W tym przypadku jest to string, czyli słowo/zestaw znaków wpisany przez użytkownika w oknie konsoli i zatwierdzony naciśnięciem Enter.

Wykorzystajmy tę wiedzę i przypiszmy to co poda użytkownik do nowej zmiennej, żeby było intuicyjnie nazwiemy ją widthFromUser. Podobnie postąpmy z wysokością przekroju – heightFromUser.

Pobranie wartości od użytkownika i przypisanie ich do dwóch nowych zmiennych w żaden sposób nie wpłynęło na argumenty metody GetMomentOfInertia – one dalej są przypisane na sztywno. Pora to zmienić – zróbmy coś szalonego!

Wykorzystałem nowe zmienne aplikując je jako argumenty naszej metody. Skończyło się to przepiękną czerwoną falą wskazującą, że coś w tym miejscu jest nie tak. Właśnie stworzyłem coś, czego nie da się skompilować, co nie ma prawa zadziałać przy próbie uruchomienia. Visual Studio zrozumiało to przede mną, a nawet podpowie nam w co najmniej dwóch miejscach jaka jest prawdopodobna przyczyna.

W dolnej części VS wyświetlane są błędy, którymi obarczony jest nasz program.

Chciałem wlać do baku naszej metody świeżo wydobytą ropę naftową co niechybnie skończyłoby się zniszczeniem silnika. Najpierw powinienem dokonać odpowiedniej rafinacji surowego stringa pobranego przez użytkownika i zamienić go na coś, co nasza metoda toleruje na wejściu, czyli na liczbę zmiennoprzecinkową – typ double.

Procedura zamiany stringów na typy liczbowe nazywa się parse’owaniem i posiada dwa bazowe warianty:

        string widthFromUser = Console.ReadLine();

        double width = double.Parse(widthFromUser);

        double saferWidth;
        bool converstionStatus = double.TryParse(widthFromUser, out saferWidth);

Wariant pierwszy, szybki to trzecia linijka z powyższego listingu. Przy jej pomocy wydajemy proste polecenie: zamień argument metody Parse na typ double.

Wariant drugi, czyli linijki piąta i szósta jest bardziej zachowawczy. Mówi on: spróbuj zamienić wartość tekstową na liczbę zmiennoprzecinkową.

Różnicę pomiędzy ‘zrób’ a ‘spróbuj’ odczujesz w sytuacji, gdy użytkownik sprawi Ci psikusa i nie poda liczby tylko losowy ciąg znaków, którego nie da się zamienić na liczbę. Wtedy zwykłe double.Parse wywoła wyjątek, czyli nieoczekiwany stan aplikacji, który w tym wariancie, zakończy nagle działanie programu. double.TryParse w kryzysowym momencie nie rzuci wyjątkiem, tylko elegancko zwróci wartość false jako dowód, że konwersja się nie powiodła, że coś poszło w tym miejscu nie tak. Zawsze zakładaj, że użytkownik skorzysta z Twojego narzędzia niezgodnie z zakładanym przeznaczeniem. Oczekuj nieoczekiwanego a Twoje życie będzie prostsze – Grzegorz Coehlo.

Obsługę wejścia użytkownika proponuję rozwiązać w poniższy sposób:

        string widthFromUser = Console.ReadLine();
        double width;
        if (!double.TryParse(widthFromUser, out width))
        {
            Console.WriteLine($"Wartość {widthFromUser} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            return;
        }

Skorzystałem z bezpiecznego TryParse, jednocześnie informując użytkownika dlaczego program zakończył pracę. Jeżeli wszystko poszłoby ok, to zawartość instrukcji if nie zostanie wykonana – zwróc uwagę na wykrzyknik przed wywołaniem metody double.TryParse. W normalnych warunkach, gdy konwersja się powiedzie TryParse zwróci wartość true, my chcemy obsłużyć przypadek przeciwny i to właśnie wykrzyknik niejako zamienia true na false i odwrotnie.

Teraz uważaj, założ na głowę kask budowlańca, bo za chwilę zwali nam się na głowę cały programistyczny świat grzmiąc ‘nigdy, przenigdy nie programuj metodą Kopijego-Pejsta‘. Ja założyłem, dlatego przedstawiam Ci kolejny listing:

        string widthFromUser = Console.ReadLine();
        double width;
        if (!double.TryParse(widthFromUser, out width))
        {
            Console.WriteLine($"Wartość {widthFromUser} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            return;
        }

        string heightFromUser = Console.ReadLine();
        double height;
        if (!double.TryParse(heightFromUser, out height))
        {
            Console.WriteLine($"Wartość {heightFromUser} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            return;
        }

Widzisz uderzające podpobieństwo przy drugiej próbie konwersji tekstu na liczbę? Wykorzystaliśmy dokładnie ten sam ‘algorytm’ zmiany, zwracamy ten sam komunikat, złamaliśmy dobrą zasadę DRY, czyli don’t repeat yourself. Kopiowanie fragmentów kodu w różne miejsca niemal zawsze mści się po pewnym czasie, gdy okazuje się, że musimy coś zmienić i zapominamy w ilu miejscach znajduje się dana funkcjonalność. Jak zrobić to porządniej? Wyekstrahować funkcjonalność do nowej metody, na przykład tak:

    public static double? GetDoubleFromUser(string inputValue)
    {
        double result;
        if (double.TryParse(inputValue, out result))
            return result;
        else
        {
            Console.WriteLine($"Wartość {inputValue} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            return null;
        }
    }

Zwróć uwagę na znak zapytania przy deklaracji zwracanego typu z naszej nowej metody. Zapis double? oznacza, że dana metoda może, ale nie musi zwrócić liczby. W przypadku niepowodzenia zwrócimy wartość null, którą traktuj jako nieistniejącą.

Po stworzeniu nowej metody GetDoubleFromUser możemy nasz program zapisać w poniższej postaci:

using System;

public class Program
{
    public static void Main()
    {
        Console.WriteLine("Podaj szerokość przekroju:");
        string widthFromUser = Console.ReadLine();
        double? width = GetDoubleFromUser(widthFromUser);
        if (!width.HasValue)
            return;

        Console.WriteLine("Podaj wysokość przekroju:");
        string heightFromUser = Console.ReadLine();
        double? height = GetDoubleFromUser(heightFromUser);
        if (!height.HasValue)
            return;

        double momentOfInertia = GetMomentOfInertia(width.Value, height.Value);

        string message = string.Format("Moment bezwładności przekroju wynosi {0:N2}", momentOfInertia);
        Console.WriteLine(message);

        Console.ReadLine();
    }

    public static double? GetDoubleFromUser(string inputValue)
    {
        double result;
        if (double.TryParse(inputValue, out result))
            return result;
        else
        {
            Console.WriteLine($"Wartość {inputValue} nie może zostać zamieniona na liczbę. Koniec działania programu.");
            Console.ReadLine();
            return null;
        }
    }

    public static double GetMomentOfInertia(double b, double h)
    {
        double momentOfInertia = (b * Math.Pow(h, 3)) / 12;

        return momentOfInertia;
    }
}

Dodałem wskazówki/zachęty dla użytkownika przed właściwym odczytaniem wartości – zawsze to milej gdy program podpowie co teraz powinno się stać.

Nasza pierwsza aplikacja konsolowa działa, pobiera od użytkownika wejście, konwertuje wartości, wykonuje zadane przez nas działanie (wylicza moment bezwładności) i zwraca rezultat do konsoli. Nie jest źle, ale malkontenci powiedzą, że na kalkulatorze byłoby szybciej. W kolejnej części wzbogacimy naszą procedurę o kolejne elementy tak, żeby team Casio zaczął zmieniać zdanie 🙂

Categories
Ogólne

Visual Studio – instalacja

Wpis [part not set] z 8 w serii C# - podstawy

Zaprezentowany w poprzednim poście Fiddle jest bardzo szybkim sposobem na przedstawienie innym własnej koncepcji. Niestety, raczej małej koncepcji. Do wydajnej pracy nad dużym projektem potrzebujemy narzędzia odpowiedniej klasy, czegoś co umożliwi nam sprawne poruszanie się po wielu plikach źródłowych, co będzie w interaktywny sposób wspierać naszą pracę.

Wyobraź sobie, że szef w nagrodę zrzucił na Ciebie wykonanie dokumentacji warsztatowej dla obiektu złożonego z więcej niż 1000 elementów wysyłkowych. Masz ten komfort, że możesz wybrać: wolisz pracować na tysiącu niepowiązanych plikach .dwg czy skorzystać z jednego modelu zawierającego wszystkie elementy? Jeżeli jeszcze się zastanawiasz to dorzucam w gratisie automatyczne sprawdzanie kolizji pomiędzy elementami 😉

W programowaniu nasze BIM’owe rozwiązania są standardem od … bo ja wiem, 30 lat? W przypadku C# najpowszechniej używanym IDE, czyli kombajnem do programowania jest Microsoft Visual Studio, które zainstalujemy w najnowszej wersji sygnowanej rokiem 2022.

Przejdź na stronę Microsoftu i wybierz Pobierz Visual Studio, pod przyciskiem pojawi się lista wyboru. Do naszych zastosowań odpowiednia będzie wersja Community 2022.

Po chwili pobrany zostanie wstępny instalator programu, uruchom go, zaakceptuj wstępne warunki i poczekaj na pobranie brakujących plików.

Visual Studio jest wszechstronnym narzędziem, wykorzystywanym do wielu celów. Tego typu programy, które umożliwiają sprawne tworzenie kodu, jego kompilowanie i testowanie noszą nazwę IDE, czyli integrated development environment. W naszym wypadku wystarczy, że zainstalujemy zestaw .NET desktop development, który zawiera pakiety niezbędne do tworzenia programów działających stacjonarnie w C#. Jeżeli w przyszłości będziesz chciał rozwinąć skrzydła – bez obaw, w dowolnym momencie możesz wrócic do tego ekranu modyfikując instalację i wybrać brakujące składniki.

Po wybraniu zestawu i upewnieniu się, że w docelowej lokalizacji na dysku twardym masz wystarczająco miejsca wybierz Install i poczekaj na koniec procesu – chwilę to potrwa.

Ok, proces instalacji dobiegł końca, masz Visual Studio na komputerze. Widzisz teraz przed sobą okno zachęcające do zalogowania/zarejestrowania:

Jeżeli posiadasz już Microsoftowe konto to zachęcam do zalogowania się przy jego pomocy. Jeżeli jeszcze go nie posiadasz to … zarejestruj się. Dlaczego? Dostaniesz Visual Studio Community dożywotnio a nie na 30-dniowy okres testowy. Hmm, tylko czy aby nie ma tu jakiegoś haczyka?

Microsoft robi dużo żeby zachęcić ludzi do wejścia w świat programowania. To postępowanie wynika oczywiście z pobudek komercyjnych – z biegiem czasu zobaczysz jak szeroką paletę płatnych usług/narzędzi ma w swojej ofercie gigant z Redmond. Tę strategię porównałbym do dobrze nam znanego Autodesku, który oferuje darmowe wersje swoich programów na czas studiów. Przywiązuje w ten sposób przyszłych pracowników do swoich rozwiązań, kształtując niejako skillset kolejnych pokoleń inżynierów, którzy zawsze chętnie wrócą do tego co im znane, no może w sytuacji obowiązkowych subskrypcji zwrócą się w stronę chińskich klonów, ale to temat na inny wpis.

I tu dochodzimy do ograniczeń darmowych wersji dużych programów. W przypadku Autodesku są to zazwyczaj znaki wodne na wydrukach + monitorowanie użycia ich edukacyjnych wersji w środowiskach biznesowych. Microsoft nie jest tu wyjątkiem, wersja Community ma swoje ograniczenia, o których warto wiedzieć:

  • zawsze możesz przy jej pomocy brać udział w projektach open source (takich jak zbliżające się biblioteki LetsConstructIT)
  • możesz jako indywidualny programista tworzyć własne aplikacje, równiez komercyjne
  • jeżeli Twoja firma posiada mniej niż 250 komputerów oraz jej przychód nie przekracza 1 mln USD to możesz tworzyć przy jej pomocy aplikacje komercyjne
  • jeżeli pracujesz w większej firmie to stać ją na zakup wersji Professional za 2 tys złotych

Tak w skrócie wygląda polityka Microsoftu. Ograniczenia licencyjne wersji Community mogą wykluczyć ją z zastosowań w większych firmach, których dział finansów niechętnie patrzy na inwestycje w rozwój. Teoretycznym rozwiązaniem jest skorzystanie wtedy z mniejszego brata Visual Studio, czyli programu Visual Studio Code, który jest zawsze darmowy, a umożliwia prawie to samo co zwykły VS. Niestety prawie robi różnicę :/ Z perspektywy inżyniero-programisty największą bolączką będzie brak edytorów wizualnych do tworzenia okien dialogowych z wykorzystaniem WinForms / WPF oraz problemy z debugowaniem aplikacji wykorzystujących .NET Framework.

Ciąg dalszy historii będę prowadził w oparciu o Visual Studio. Wierzę, że korzyści które niedługo dostrzeżesz będą wystarczające aby przekonać Twoich zwierzchników, że warto (o ile jest taka potrzeba) zainwestować w środowisko programistyczne.

Categories
Ogólne

C# – przedsionek

Wpis [part not set] z 8 w serii C# - podstawy

Jesteś wciąż ze mną? Udało mi się wzbudzić Twoją ciekawość? Ok, to lecimy dalej 🙂

Chcę żebyś pokonał pierwsze demony, pierwsze wewnętrzne blokady, które szepczą Ci: “nie nadaję się”, “ja? programista? lol!”, “dobra, dobra, piątkowa wysyłka sama się nie zrobi”.

W tym celu napiszemy Twój pierwszy program … w przeglądarce. Skorzystamy z darmowego kompilatora online .NET Fiddle.

Chcemy napisać program, który będzie obliczał moment bezwładności prostokąta. Podstawy wytrzymałości materiałów, czyli (b*h^3)/12.

Przejdź do Fiddle i zastąp domyślną zawartość edytora poniższym kodem:

using System;
                    
public class Program
{
    public static void Main()
    {
        double b = 30;
        double h = 60;
        
        double momentOfInertia = (b*Math.Pow(h,3))/12;
        
        string message = string.Format("Moment bezwładności wynosi {0:N2}", momentOfInertia);
        Console.WriteLine(message);
    }
}

Po wybraniu przycisku Run zobaczysz w dolnej części rezultat działania naszego programu, czyli komunikat o wartości obliczonego momentu bezwładności prostokąta.

“Eee, takie rzeczy to ja mogę na moim Casio zrobić w trzy sekundy, co Ty w ogóle pokazujesz?”

Przyznaję, powyższy kod nie należy do skomplikowanych, ale stanowi bazę historii, którą planuje opowiedzieć w trakcie kolejnych postów, więc proszę – bądź wyrozumiały 🙂

C# jest językiem silnie typowanym, co możemy zaobserować w linijkach 7, 8. Zmienne, które tworzymy muszą posiadać określony typ, czyli rodzaj danych, która będzie się kryła pod daną nazwą. W powyższym przypadku zdecydowałem się na wykorzystanie typu double, który służy od deklaracji liczb zmiennoprzecinkowych.

Dobór nazw zmiennych jest wyborem programisty, to on ma być w stanie zrozumieć co dzieje się w danym miejscu kodu po dłuższej przerwie. Dlatego użyłem nazw b oraz h – są one czytelne dla inżynierów znających rozpatrywany problem. Gdybym zdecydował się na nazwy zmienna1 oraz zmienna2 to po tygodniu nie miałbym pojęcia z czym kojarzyć te pojęcia.

Zwróć uwagę na linijkę 10 i zapis momentOfInertia. Zapisanie frazy w sposób ciągły, zaczynając od małej litery a następnie stosując wielką literę dla kolejnych słów to przykład notacji ‘camelCase’, która jest podstawą w C# oraz Javie. Będziemy jej używać do nazywania zmiennych o charakterze lokalnym, które żyją wyłącznie w ramach danej metody.

Hej, czy ja powiedziałem ‘metody’? Co to ta ‘metoda’?

O metodach pomyśl jak o funkcjach, czyli zamkniętych zbiorach procedur, które coś robią. Słowo coś jest tutaj idealne – metoda może, ale nie musi zwracać wartości. Spójrz na linijkę 5’tą. Widzisz tam zbiór nic nie mówiących słów: public static void Main(), które powinieneś zinterpretować jako:

  • public – ta metoda jest jawna, publiczna, widoczna dla innych aplikacji,
  • static – metoda jest statyczna, można jej użyć ‘z palca’, bez nadmiernej pracy przy tworzeniu obiektów (o nich w kolejnym poście),
  • void – metoda zwraca czarną otchłań, w której pogrążył się (tymczasowo) Gandalf; ta metoda tylko ‘coś’ robi, nie zwraca żadnej wartości,
  • Main() – metoda nazywa się Main

Zastanawiasz się pewnie teraz jak stworzyć metodę, która zwróci wartość. Właśnie taki jest mój plan, o spójrz na ten listing:

using System;
                    
public class Program
{
    public static void Main()
    {
		double momentOfInertia = GetMomentOfInertia();
        
        string message = string.Format("Moment bezwładności wynosi {0:N2}", momentOfInertia);
        Console.WriteLine(message);
    }
	
	public static double GetMomentOfInertia()
	{
        double b = 30;
        double h = 60;
        
        double momentOfInertia = (b*Math.Pow(h,3))/12;
		
		return momentOfInertia;
	}
}

Przeorganizowałem nasz program wydzielając z niego nową metodą – GetMomentOfInertia(). Ta metoda zwraca liczbę zmiennoprzecinkową typu double, co jest zdefiniowane w dwóch miejscach:

  • deklaracja metody składa się z public static double GetMomentOfInertia() i to właśnie pogrubiona cześć mówi kompilatorowi czego należy się spodziewać po tej metodzie,
  • w linii 20 użyłem słowa return kończąc działanie metody zwrotem wyznaczonej wartości.

Patrzę na ten przykład i dochodzę do wniosku, że fajnie, fajnie, mamy program, który za każdym razem zwraca tę samą wartość. Spójrz na linijki 15 i 16 – znajduje się ‘sztywne’ przypisanie wartości do zmiennych b i h. Przyznaję, mało użyteczny sposób.

Gdy pomyślisz o metodach jak o funkcjach matematycznych to szybciej zrozumiesz koncept argumentów metod, czyli wartości wejściowych, które ustawiają początkowe warunki dla wykonywanego kodu. Dobrze by było, gdyby metoda GetMomentOfInertia była gotowa przyjąć dwa argumenty opisujące nasz problem. Tylko jak to zrobić?

using System;
                    
public class Program
{
    public static void Main()
    {
		double momentOfInertia1 = GetMomentOfInertia(30, 60);
		double momentOfInertia2 = GetMomentOfInertia(20, 50);
        
        string message1 = string.Format("Moment bezwładności 1 wynosi {0:N2}", momentOfInertia1);
        Console.WriteLine(message1);
        string message2 = string.Format("Moment bezwładności 2 wynosi {0:N2}", momentOfInertia2);
        Console.WriteLine(message2);
    }
	
	public static double GetMomentOfInertia(double b, double h)
	{
        double momentOfInertia = (b*Math.Pow(h,3))/12;
		
		return momentOfInertia;
	}
}

Analizę tego przykładu zacznij od linijki 16’tej – w deklaracji metody, wewnątrz nawiasów pojawiła się informacja o oczekiwanych argumentach, czyli: (double b, double h). Dzięki zmianie sztywnych wartości z wnętrza metody na rzecz jej argumentów możemy zacząć wykorzystywać ją wielokrotnie, dla róznych wartości początkowych. Dlatego zawartość naszej początkowej metody Main() została wzbogacona o wyznaczenie wartości drugiego momentu bezwładności.

—————

I teraz proszę, odpowiedz sobie na pytanie, czy powyższe wywody przekraczają zdolności poznawcze inżyniera budownictwa po 5-letnich studiach w trakcie których poznawał tensory, całki potrójne, kombinatorykę wg eurokodów, półkę plastyczną, itd. No właśnie, dlatego proszę Cię, jeśli dalej myślisz ‘programowanie jest dla mnie za trudne’ to spójrz na swoją drogę, na to ile już potrafisz, co przeszedłeś. Ja nie mam wątpliwości, że ‘ogarniesz’ 😉

Jeżeli czujesz niedosyt – to wspaniale, dopiero się rozkręcamy!

Categories
Ogólne

Języki programowania

Wpis [part not set] z 4 w serii Motywacja

Java, C++, C#, Visual Basic, Python, JavaScript, … – dlaczego tego tyle jest?

Heh, a dlaczego ludzie porozumiewają się w 7 tysiącach języków? 🙂

Informatyka to stosunkowo młoda dziedzina, która stale ewoluuje. Wraz z ciągłym rozwojem okazuje się, że rozwiązania z poprzednich lat nie zdają egzaminu zwiększonego popytu na usługi, stale rosnącej grupy użytkowników komputerów/sieci. W odpowiedzi na nowe potrzeby powstają nowe języki programowania lub istnieje konieczność znacznej modyfikacji już istniejącego języka, która wyklucza wsteczną kompatybilność z poprzednią wersją.

Wyobraź sobie, że jako administrator NASA planujesz program Apollo. Wiesz, że potrzebujesz zestawu narzędzi wspomagających obliczenia trajektorii lotu, że lot na księżyc powinien w maksymalnym stopniu polegać na przetestowanych procedurach. Zastanawiasz się w jakim języku zlecić stworzenie odpowiednich programów. W tym czasie jedynym sensownym rozwiązaniem jest Assembler – skomplikowany, niskopoziomowy język programowania. Wydajesz więc miliony $$ amerykańskich podatników na rozwój oprogramowania w tym języku.

Gdy opada kurz wzbity przez lądownik księzycowy wśród programistów rośnie na popularności COBOL, oferujący wyższą efektywność pracy, będący mniej siermiężny niż Assembler. Świetnie, tylko, że ogromne fundusze zostały władowane w rozwiązanie napisane w Assemblerze i teraz co, przepisujemy program Apollo na nowy język?

Albo inny przykład z naszego poletka. Efektywność energetyczna starych kamienic w centrach miast pozostawia wiele do życzenia. Spełnienie przez nie aktualnie wymaganych współczynników przenikania ciepła jest często niemożliwe. Czyli co? Burzymy i w to miejsce stawiamy obiekt z trójwarstwowymi ścianami? Czy może bardziej delikatnie: obklejamy dawną cegłę styropianem, metoda lekka-mokra i malujemy na pstrokate kolory tak powszechne na naszych osiedlach? Nie zawsze to co nowsze jest na tyle dobre, aby wymazać gumką poprzednie rozwiązania, zwłaszcza jeżeli poniesiono już ogromne nakłady kapitału.

Co roku pojawiają się kolejne języki programowania, które odpowiadają na specyficzne potrzeby grupy programistów. Jednocześnie niezbędne jest utrzymywanie i rozwijanie już napisanych programów – Ty jako użytkownik oczekujesz ciągłego rozwoju narzędzi z których korzystasz. Dlatego pojawianie się nowego języka nie usuwa z użycia poprzednich rozwiązań – ani programiści nie zmienią swojej specjalizacji na pstryknięcie palcami ani kod magicznie nie zostanie przetłumaczony na nowy język. Nie wspominając o testach skomplikowanych systemów.

Popularność języków programowania

Jeżeli ciekawi Cię, który język programowania jest najpowszechniej wykorzystywany to możesz odwiedzić stronę firmy TIOBE, która co miesiąc tworzy zestawienie języków ze względu na powszechność użycia. W chwili pisania tego posta TOP 3 to: Python / C / Java

Powyższy ranking nie do końca odpowiada na pytanie, które technologie są obecnie najbardziej poszukiwane na rynku pracy. W celu próby uzyskania odpowiedzi na takie pytanie polecam skorzystać z wyszukiwarki ofert pracy na portalu NoFluffJobs. Po wykonaniu paru kliknięć otrzymasz wyniki:

  • JavaScript – 677 ofert pracy
  • Java – 635
  • Python – 471
  • .NET (C#) – 292

Czyli co, uczymy się Javy* bo w sumie to 1300 ofert pracy? 😉

Porównywanie liczby aplikacji napisanych w C do obecnego popytu na programistów JavaScript porównałbym do liczby budynków w tradycyjnej technologii murowanej a zapotrzebownaia na konstrukcje szkieletowe. Wierzę, że teraz czujesz, że wybór języka, którego chcesz się nauczyć warto rozłożyć na czynniki pierwsze.

Języki programowania w budownictwie

Jestem zwolennikiem stopniowego zdobywania nowych umiejętności, najlepiej w ramach 8 godzin pracy. To założenie stanowi podstawowe założenie całego Let’s construct IT i rzutuje na wybór C# jako naszego głównego języka.

Dlaczego, zapytasz. Rozważmy dwa scenariusze dla początkującego, który pracuje w biurze projektowym, często łapie nadgodziny a po powrocie do domu czeka na niego dziecięce pandemonium:

  • ‘kurczę, ta JavaScript jest wszędzie, znajomy w to wszedł, bootcampy kuszą już za ~10k PLN, ofert pracy bez liku, wchodzę w to!’
    Super, tylko na jak długo wystarczy Ci motywacji, żeby uczyć się w czasie wolnym po 8 godzinach przepychanek z architektem, próbach dalszej optymalizacji tego już optymalne i odpowiadaniu na telefony z budowy, gdy znów coś się spier*? To kwestia indywidualna, ale moje doświadczenie wskazuje, że po 4 kawie zasób energetyczny nie odnawia się już liniowo, zaczynasz po prostu robić bokami.
  • ‘hmm, mamy w biurze x AutoCADów, y Revitów, z Tekli. Słyszałem, że można rozszerzyć możliwości tych programów, że mają jakieś API, może spróbuję, zobaczę jak i co.’
    To była moja droga (więc oczywiście musi być najlepsza, schemat identyczny jak z dziećmi 😛 ), sensowna choć dłuższa. W stosunku do pierwszego podejścia użyłbym porównania: sprint vs bieg długodystansowy. Nie każdy potrafi/ma warunki to szybkiego biegu. Życiowe ‘opony, które chwilowo trzymają nas za kolana skłaniają do rozłożenia tego wysiłku w dłuższym czasie.

Dlatego zachęcam do zapoznania się z tabelą zbierającą znane mi programy budowlane oraz języki programowania z których można korzystać w celu tworzenia wtyczek. Jeżeli poniższa lista jest niepełna/zawiera błędy daj mi znać 🙂

ProgramJęzyki API
AutoCAD + klonyAutoLisp, C++, C#
RevitC#, C++, Python
Tekla StructuresC#
AllplanPython
ArchiCADC++
Autodesk RobotC#, C++
RFEMC#, Python
AxisVMC#, C++, Python, Delphi
SCIA EngineerC#, Python

W powyższej tabeli nie uwzględniłem VBA czyli Visual Basica wkomponowanego w pakiet Office, w biurach projektowych wykorzystywanego głównie z poziomu Excela. Proszę, nie wchodź w to świadomie, to droga w jedną stronę, jego czas się skończył.

Druga ważna uwaga to fakt, że C# w powyższym zestawieniu może być zastąpiony VB.NET. Oba języki wykorzystują to samo środowisko uruchomieniowe .NET. Jednak liczba materiałów/samouczków/dokumentacji/ofer pracy zdecydowanie przechyla tę szalę (C# vs VB.NET) na rzecz C#.

C++

Pomimo faktu, że niemal każdy z dużych programów budowlanych jest napisany w tym języku – odradzam wybierania go jako pierwszy wybór. C++ jest z cała pewności ‘hard to master’, niestety nie spełnia pierwszej cześci tej maksymy, czyli ‘easy to learn’.

Python

Rozsądny wybór o ile zestaw programów z których korzystasz w biurze go wspiera. Python jest na fali, można w nim postawić stronę internetową, serwer, aplikację desktopową i wtyczkę do Allplana. Duża społeczność inżynierów i bibliotek matematycznych jest kolejną zaletą. Od niego zaczynałem, daaaaaaawno temu. Wspomnienia mam pozytywne 🙂

Punktem odniesienia Pythonowego ‘budowlano-programisty’ jest Łukasz Laba, którego w sieci można znaleźć pod adresem https://github.com/lukaszlaba

C#

Najcześciej spotykany język API w budownictwie. Odpowiedź Microsoftu na sukces Javy, która pozwoliła firmie z Redmond stworzyć cały ekosystem usług. Wykorzystywany wszędzie, dla mnie podstawowe źródło dochodów.

I to właśnie w C# będę przedstawiał zdecydowaną większość treści na tym blogu, choć w późniejszej fazie może zdarzyć coś frontendowego.

*Java vs JavaScript

To dwa diametralnie różne języki programowania. Zbieżność nazw wynika z zabiegu marketingowego, który został podjęty w 1995 roku przez firmę Netscape (łapka w górę kto pamięta tego dinozaura). W dużym uproszczeniu JavaScript jest królem frontendu, czyli stron WWW. Java z kolei … jest wszędzie (pamiętny ekran w trakcie instalacji), natomiast najcześciej spotykany jest po stronie backendu przeróżnych poważnych organizacji, czytaj banków.

Pamiętaj, nie bądź noob’em: Java != JavaScript 😉

Categories
Ogólne

Jesteś programistką/ą

Wpis [part not set] z 4 w serii Motywacja

Wait, what?

Prawdopodobnie ukończyłaś/eś lub jesteś w trakie studiów budowlanych. Pozycjonujesz się jako inżynier budownictwa i dokładnie tak zawodowo widzi Cię Twoje otoczenie.

Eurokody, kosztorysy, WZ-tki, umowy, rzuty, przekroje, IFC’ki – to Twój chleb powszedni. Związek powyższych haseł z programowaniem wydaje się być równie odległy co Mars.

I tak dochodzimy do mojego rozumienia słowa “programista”, które zauważ, pozbawione jest doprecyzowania, wskazania jakiego języka używasz, czy zajmujesz się stroną wizualną czy logiką aplikacji.

Dla mnie programista to ktoś, kto potrafi rozwiązać lub znaleźć rozwiązanie problemu, kto jest w stanie zamienić ludzkie czynności na zbiór precyzyjnych instrukcji, kto potrafi zalgorytmizować problem.

Przypomnij sobie Twoje studia, spójrz na swoją pracę, niemal zawsze tworzysz lub wykorzystujesz reguły, algorytmy. Prześledź proszę niniejsze sytuacje:

  • dostajesz wstępną geometrię obiektu, zakładane obciążenia, uwarunkowania środowiskowe. Po wykonaniu n-kroków wiesz jakie przekroje elementów są niezbędne, jaka klasa stali/betonu będzie odpowiednia, ile prętów trzeba włożyć w przekrój, żeby uniknąć scenariusza ‘Andrzej, to je*’,
  • dostajesz wstępną dokumentację obiektu, wyłuskujesz z niej przyjęte gabaryty elementów, dopytujesz o szczegóły, zliczasz elementy, kalkulujesz za ile warto by wejść w ten temat.

Projekt po projekcie – podążasz podobnymi torami, na pewnym poziomie abstrakcji byłbyś w stanie wydzielić zasadnicze czynności, które są niezbędne do zakończenia zadania, jesteś w stanie zalgorytmizować Twój problem.

Prawdopodobnie robisz to w języku ‘ludzkim’, opowiadając Twoim nowym kolegom/koleżankom na czym polega praca, konsultując ze starszymi wyjadaczami czy podjęte przez Ciebie działania są odpowiednie. Tylko czy aby na pewno robisz to wyłącznie w języku ‘ludzkim’?

Excel

Porównaj proszę zestaw pustych komórek, które wyświetlają się po utworzeniu nowego pliku Excela z Twoim najbardziej zaawansowanym arkuszem do obliczeń/kosztorysowania. Ździebko inne, prawda? 🙂

Według mnie aktywne korzystanie z Excela jest pierwszym wyznacznikiem, że nadajesz się na programistę. Skoro na pewnym etapie doszedłeś do wniosku, że warto przenieść ręczne obliczenia porozrzucane na kilku kartkach A4 połączonych Twoim studenckim kalkulatorem Casio w ramy ustrukturyzowanego arkusza obliczeniowego to potrafisz algorytmizować problemy.

Aha, nie przejmuj się, że Twój arkusz potrafisz zrozumieć tylko Ty. Efekty pracy początkującego programisty zazwyczaj też takie są. Dopiero praca zespołowa niejako przymusza nas do wyjścia poza ramy własnych skojarzeń/przyzwyczajeń na rzecz dobrych praktyk.

Mathcad/SMath/Scilab/Matlab

Czyli zestaw mniej popularnych narzędzi, które jednak pojawiają się w różnych firmach budowlanych. Do jednego worka wrzuciłem całą rodzinę programów, jednak jeżeli miałeś/masz okazję pracować w którymkolwiek z nich to jesteś już o krok od wkroczenia na ścieżkę programisty. Każde z nich służy do instruowania procesora w Twoim komputerze jak powinny zostać wykonane obliczenia lub w jaki sposób mają zostać zaprezentowane ich wyniki.

W świecie idealnym powyższe programy powinny służyć do szybkiego prototypowania nowych pomysłów, algorytmów. Sprawny inżynier może z ich pomocą w stosunkowo krótkim czasie sprawdzić sensowność nowej koncepcji. Po pozytywnej pierwszej fazie warto zastanowić się co dalej, jedną z możliwości jest przejście na właściwe programowanie z wykorzystaniem jednego z dostępnych języków.

Grasshopper/Dynamo

Programowanie wizualne to stale rosnący trend ostatnich lat. Żeby zrozumieć fenomen tego podejścia polecam zapoznać się ze Scratch, którego uczą się uczniowe podstawówek.

Niesamowite możliwości testowania, generowania dawniej niemożliwych brył, spinania procesów przy pomocy bloków funkcyjnych – bajka, która w wielu inżynierskich przypadkach wystarczy, jednak w moim odczuciu ma istotne ograniczenia.

Warto odnotować fenomenalną pracę wykonywaną przez Krzysztofa Wojsława, który dzieli się wiedzą związana z Grasshopperem na stronach learngrasshopper.com oraz bimcorner.com. Drugim wartym polecenia źródłem Grasshopperowej wiedzy jest Marcin Woch, który prowadzi bloga: bimdigitz.com

Jeżeli korzystasz już z powyższych narzędzi to pewnie odczuwasz pewien niedosyt – wtedy świetnym krokiem jest wejście w ‘prawdziwe’ programowanie. Algorytmizować problem już potrafisz 🙂

Categories
Ogólne

Plany

Wpis [part not set] z 4 w serii Motywacja

W poprzednim wpisie opisałem motywacje, które skłoniły mnie do założenia społeczności Let’s construct IT. W tym wątku chcę przedstawić jakie plany chcę zrealizować w początkowym okresie.

Początkujący

Przez 8 lat pracowałem w różnych firmach około-budowlanych. W każdej z nich spotkałem się z osobami, które chciały wejść w świat programowania. Zawsze padał zestaw pytań: jak/kiedy/czy się nadaję? Pula wolnego czasu jakim dysponujemy maleje wraz z kolejnymi etapami życia. Posiadanie partnera czy narodziny dziecka to momenty reorganizujące nasze życie. Droga do programowania studenta-lekkoducha będzie zupełnie inna niż matki z trójką dzieci. W moim odczuciu kluczem do sukcesu jest stopniowe przekształcanie obecnego źródła dochodu w nowe – dlatego droga dla początkujących, którą będę prezentował będzie związana z codzienną pracą inżyniera budownictwa. W ten sposób dostarczysz w Twoim obecnym miejscu pracy nową wartość, zyskasz nowe kompetencje, które są obecnie cenne a które w przyszłości mogą stać się dla Ciebie trampoliną do nowego startu w innej branży.

Z myślą o początkujących chcę:

  • przekonać Cię, że już jesteś programistą,
  • pomóc w postawieniu pierwszych kroków w C#,
  • nakierować na wartościowe, darmowe kursy skupiające się na podstawach programowania,
  • wejść z Tobą w świat rozszerzania możliwości programów, z których korzystasz na co dzień: wszelkich CADów, Tekla Structures, Autodesk Robot, AxisVM,
  • zachęcić do założenia konta i współdziałania w ramach github.com,
  • przedstawić rolę testowania w programowaniu.

Zaawansowani

Jesteś “budowlano-programistą”, tworzysz programy/wtyczki na wewnętrzny użytek Twojego biura. Zastanawiasz się co dalej, szukasz wyzwań lub uznania i zrozumienia wśród podobnych Tobie. Wierzę, że prędzej czy później usłyszysz o naszej społeczności i z zainteresowaniem sprawdzisz nad czym pracujemy. Być może włączysz się wtedy w repozytoria poświęcone nowym narzędziom lub pozostaniesz w trybie pasywnym – skorzystasz z owoców naszej pracy do rozwoju wewnętrznych narzędzi w Twoim biurze. Let’s construct IT to organizacja non-profit, której głównym motorem jest zwiększenie grona osób, które rozumieją budownictwo i potrafią programować. Będzie mi bardzo miło, jeżeli włączysz się w te działania 🙂

Co możesz zyskać:

  • poznasz innych budowlano-programistów, co otworzy przed Tobą nowe możliwości,
  • biorąc udział w pracach w ramach repozytoriów na github’ie zbudujesz swój profil w ramach tej strony, który będzie Twoją wizytówką,
  • wykorzystasz narzędzia/biblioteki nad którymi będziemy pracować w Twoim biurze projektowym. Zdajesz już sobie sprawę jak przyjemnie jest sklejać różne biblioteki w celu uzyskania pożądanego celu. Pomyśl, że mógłbyś pobrać jako paczkę nugetową bibliotekę pokrywającą podstawowe obliczenia wytrzymałościowe w ramach EC2/EC3. Czujesz tę moc, prawda?

Obserwatorzy

Jesteś menadżerem zarządzającym zespołem inżynierów, nie masz czasu ani ochoty na naukę programowania. Czujesz jednak, że świat Ci odjeżdża, że to całe BIM’banie nie przynosi obiecanych przez marketing korzyści, że bez cyfryzacji procesów/ludzi Twój zespół pozostanie w tyle.

Możesz zachęcić ambitnych członków Twojego zespołu do włączenia się do prac społeczności Let’s construct IT licząc, że efekty ich pracy, wzrost umiejętności przełoży się na efektywność całego zespołu. Jednocześnie z tyłu głowy zdajesz sobie sprawę z ryzyka, że oni odejdą, że przejdą do IT. To oczywiście możliwe, ale jeżeli uczynisz z nich atut, Twoją kartę przetargową i będziesz stale motywował do rozwoju to uwierz mi – zaległości w budownictwie są tak duże, że dla dobrze poprowadzonych programistów pracy wystarczy tu na lata.

Społeczność ma charakter jawny – jesteśmy zawsze otwarci na nowe pomysły i wyzwania. Jeżeli masz gotowy koncept narzędzia, które wspomoże pracę Twojego zespołu a nie masz na pokładzie osób będących w stanie go zaimplemenotwać – zgłoś się do nas. Pamiętaj tylko, że efekty naszej pracy zwiększą powszechny zasób bibliotek społeczności – korzystać z nich będzie mógł każdy. Celem Let’s construct IT jest poprawa dobrostanu całej społeczności inżynierów czego nie da się zrealizować bez dobrych pomysłów na narzędzia/wtyczki.